Skip to content

feat(java): Add ToolDefinition.fromObject() and fromClass() registration API#1779

Merged
edburns merged 6 commits into
edburns/1682-java-tool-ergonomicsfrom
copilot/edburns1682-java-tool-ergonomics
Jun 24, 2026
Merged

feat(java): Add ToolDefinition.fromObject() and fromClass() registration API#1779
edburns merged 6 commits into
edburns/1682-java-tool-ergonomicsfrom
copilot/edburns1682-java-tool-ergonomics

Conversation

Copilot AI commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Fixes #1761 .

Adds the runtime registration API that loads processor-generated $$CopilotToolMeta companion classes and returns List<ToolDefinition> with fully working handlers. This is task 4.4 in the tool ergonomics epic.

Changes

  • ToolDefinition.java — two new @CopilotExperimental static methods:

    • fromObject(Object) — for instance methods annotated with @CopilotTool
    • fromClass(Class<?>) — for static @CopilotTool methods (passes null instance)
    • Private loadDefinitions() uses Class.forName with the target classloader, setAccessible(true), and passes a configured ObjectMapper
    • ConfiguredMapperHolder — lazy-init mapper matching JsonRpcClient.createObjectMapper() (JavaTimeModule, lenient unknown-props, ISO dates, NON_NULL)
    • Throws IllegalStateException with actionable message if $$CopilotToolMeta not found — no reflection fallback
  • ToolDefinitionFromObjectTest.java — 13 gating tests with hand-written meta fixtures covering: tool discovery, schema validation, handler invocation (String/void/CompletableFuture), argument coercion (primitives + enum), default values, missing-class error, java.time deserialization via ObjectMapper contract, override flag, and ToolDefer.NONEnull JSON omission

Usage

class MyTools {
    @CopilotTool("Get weather for a city")
    public String getWeather(@Param("City name") String city) {
        return fetchWeather(city);
    }
}

// At runtime — processor must have run at compile time
List<ToolDefinition> tools = ToolDefinition.fromObject(new MyTools());
session.setTools(tools);

…thods

Adds static methods that load processor-generated $$CopilotToolMeta
classes and return List<ToolDefinition> with fully working tool
definitions (schema + invocation handlers).

- fromObject(Object): discovers tools from an instance with @copilotTool methods
- fromClass(Class<?>): discovers tools from a class with static @copilotTool methods
- Private getConfiguredMapper(): provides ObjectMapper matching JsonRpcClient config
- Throws IllegalStateException with helpful message if generated class not found
- Both methods annotated with @CopilotExperimental

Includes comprehensive test suite (ToolDefinitionFromObjectTest) covering:
- Basic discovery and schema verification
- Handler invocation for String, void, and CompletableFuture returns
- Argument coercion with primitives, String, boolean, and enums
- Default value handling when arguments are omitted
- Error case for missing generated class
- java.time argument deserialization (validates JavaTimeModule contract)
- Override tool flag propagation
- ToolDefer.NONE → null mapping (defer absent from JSON output)

Closes #1761

Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
Copilot AI changed the title [WIP] Add ToolDefinition registration API methods feat(java): Add ToolDefinition.fromObject() and fromClass() registration API Jun 24, 2026
Copilot AI requested a review from edburns June 24, 2026 03:10
@github-actions

This comment has been minimized.

@edburns edburns marked this pull request as ready for review June 24, 2026 15:39
@edburns edburns requested a review from a team as a code owner June 24, 2026 15:39
Copilot AI review requested due to automatic review settings June 24, 2026 15:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a Java runtime registration API for Copilot tools by loading processor-generated $$CopilotToolMeta companion classes and returning List<ToolDefinition> with invocation handlers, plus a new end-to-end-style test suite and fixtures to validate tool discovery and invocation behavior.

Changes:

  • Added ToolDefinition.fromObject(Object) and ToolDefinition.fromClass(Class<?>) plus reflective loading of $$CopilotToolMeta.definitions(...) with an SDK-configured ObjectMapper.
  • Added a comprehensive ToolDefinitionFromObjectTest covering discovery, schema, handler invocation patterns, argument coercion, default values, override flag, and java.time deserialization behavior.
  • Added multiple @CopilotTool fixture classes and corresponding $$CopilotToolMeta fixtures used by the new tests.
Show a summary per file
File Description
java/src/main/java/com/github/copilot/rpc/ToolDefinition.java Adds experimental tool discovery/registration APIs and shared ObjectMapper config used by generated meta handlers.
java/src/test/java/com/github/copilot/rpc/ToolDefinitionFromObjectTest.java Adds tests validating fromObject(...) behavior end-to-end, including serialization expectations.
java/src/test/java/com/github/copilot/rpc/fixtures/SimpleTools.java Fixture: basic instance tools with string/int args.
java/src/test/java/com/github/copilot/rpc/fixtures/SimpleTools$$CopilotToolMeta.java Fixture meta: tool definitions + handlers for SimpleTools.
java/src/test/java/com/github/copilot/rpc/fixtures/MultiReturnTools.java Fixture: covers String/void/CompletableFuture return patterns.
java/src/test/java/com/github/copilot/rpc/fixtures/MultiReturnTools$$CopilotToolMeta.java Fixture meta: tool definitions + handlers for MultiReturnTools.
java/src/test/java/com/github/copilot/rpc/fixtures/ArgCoercionTools.java Fixture: mixed primitive/boolean/enum argument coercion.
java/src/test/java/com/github/copilot/rpc/fixtures/ArgCoercionTools$$CopilotToolMeta.java Fixture meta: tool definitions + handlers for ArgCoercionTools.
java/src/test/java/com/github/copilot/rpc/fixtures/DefaultValueTools.java Fixture: defaulted parameter behavior.
java/src/test/java/com/github/copilot/rpc/fixtures/DefaultValueTools$$CopilotToolMeta.java Fixture meta: tool definitions + handlers for DefaultValueTools.
java/src/test/java/com/github/copilot/rpc/fixtures/DateTimeTools.java Fixture: java.time parameter support.
java/src/test/java/com/github/copilot/rpc/fixtures/DateTimeTools$$CopilotToolMeta.java Fixture meta: tool definitions + handlers using ObjectMapper conversion for LocalDateTime.
java/src/test/java/com/github/copilot/rpc/fixtures/OverrideTools.java Fixture: overriding a built-in tool name.
java/src/test/java/com/github/copilot/rpc/fixtures/OverrideTools$$CopilotToolMeta.java Fixture meta: tool definitions + handlers validating override flag propagation.

Copilot's findings

  • Files reviewed: 14/14 changed files
  • Comments generated: 4

Comment thread java/src/main/java/com/github/copilot/rpc/ToolDefinition.java Outdated
Comment thread java/src/main/java/com/github/copilot/rpc/ToolDefinition.java
Comment thread java/src/test/java/com/github/copilot/rpc/ToolDefinitionFromObjectTest.java Outdated
edburns added 4 commits June 24, 2026 13:37
The $$CopilotToolMeta test fixtures are hand-written, not processor-
generated. Update the header comment to say so accurately.
Also fix Spotless formatting in CopilotToolProcessor.java.

Addresses PR review comment about test Javadoc inaccuracy.
…Accessible

Replace reflective Method.invoke + setAccessible(true) in
ToolDefinition.loadDefinitions() with a typed interface cast.
Generated $$CopilotToolMeta classes now implement
CopilotToolMetadataProvider<T>, making them JPMS-safe and
removing the InaccessibleObjectException risk.

Addresses review comment r3468393716.
fromClass() now scans for non-static @copilotTool methods and throws
IllegalArgumentException with an actionable message listing the
offending methods and directing users to fromObject() instead.
Prevents hard-to-diagnose NullPointerException at invocation time.

Addresses review comment r3468393764.
Replace raw json.contains("defer") substring search with
ObjectNode.has("defer") to avoid false positives if another
field ever contains the substring.

Addresses review comment r3468393829.
@github-actions

Copy link
Copy Markdown
Contributor

Cross-SDK Consistency Review ✅

All 16 changed files in this PR are Java-only (java/src/). The new ToolDefinition.fromObject() / fromClass() methods are Java-idiomatic and do not create inconsistencies with other SDK implementations.

Tool registration approaches across SDKs

SDK Pattern
Node.js/TypeScript defineTool<T>() — generic function with Zod/type-inferred schema
Python @define_tool decorator — per-function, Pydantic model schema inference
Go DefineTool[T, U] — generic function with struct-based schema
.NET CopilotTool.DefineTool(Delegate, ...) — reflection-based via AIFunctionFactory
Rust ToolHandler trait — explicit per-tool trait implementation
Java ToolDefinition.create() + new fromObject() / fromClass() — annotation + processor

Rationale

The annotation-processor pattern (@CopilotTool + $$CopilotToolMeta companion class) is specific to Java's APT/javac ecosystem. Each other SDK already ships its own idiomatic "define a tool" ergonomic API:

  • Python's @define_tool decorator is already the equivalent of Java's @CopilotTool.
  • .NET delegates to AIFunctionFactory.Create for schema inference.
  • Go and TypeScript use generics for typed parameter schema generation.
  • Rust uses the ToolHandler trait.

The @CopilotExperimental gate is appropriate since the feature depends on the annotation processor running at compile time. No changes are needed in other SDKs.

Generated by SDK Consistency Review Agent for issue #1779 · sonnet46 1.2M ·

@edburns edburns merged commit d44c2b2 into edburns/1682-java-tool-ergonomics Jun 24, 2026
13 checks passed
@edburns edburns deleted the copilot/edburns1682-java-tool-ergonomics branch June 24, 2026 18:03
edburns added a commit that referenced this pull request Jun 24, 2026
…ion API (#1779)

* Initial plan

* feat(java): Add ToolDefinition.fromObject() and fromClass() static methods

Adds static methods that load processor-generated $$CopilotToolMeta
classes and return List<ToolDefinition> with fully working tool
definitions (schema + invocation handlers).

- fromObject(Object): discovers tools from an instance with @copilotTool methods
- fromClass(Class<?>): discovers tools from a class with static @copilotTool methods
- Private getConfiguredMapper(): provides ObjectMapper matching JsonRpcClient config
- Throws IllegalStateException with helpful message if generated class not found
- Both methods annotated with @CopilotExperimental

Includes comprehensive test suite (ToolDefinitionFromObjectTest) covering:
- Basic discovery and schema verification
- Handler invocation for String, void, and CompletableFuture returns
- Argument coercion with primitives, String, boolean, and enums
- Default value handling when arguments are omitted
- Error case for missing generated class
- java.time argument deserialization (validates JavaTimeModule contract)
- Override tool flag propagation
- ToolDefer.NONE → null mapping (defer absent from JSON output)

Closes #1761

Co-authored-by: edburns <75821+edburns@users.noreply.github.com>

* fix: replace misleading generated-file comment in test fixtures

The $$CopilotToolMeta test fixtures are hand-written, not processor-
generated. Update the header comment to say so accurately.
Also fix Spotless formatting in CopilotToolProcessor.java.

Addresses PR review comment about test Javadoc inaccuracy.

* fix: introduce CopilotToolMetadataProvider interface to eliminate setAccessible

Replace reflective Method.invoke + setAccessible(true) in
ToolDefinition.loadDefinitions() with a typed interface cast.
Generated $$CopilotToolMeta classes now implement
CopilotToolMetadataProvider<T>, making them JPMS-safe and
removing the InaccessibleObjectException risk.

Addresses review comment r3468393716.

* fix: validate fromClass() rejects instance @copilotTool methods

fromClass() now scans for non-static @copilotTool methods and throws
IllegalArgumentException with an actionable message listing the
offending methods and directing users to fromObject() instead.
Prevents hard-to-diagnose NullPointerException at invocation time.

Addresses review comment r3468393764.

* fix: use parsed JSON tree for defer-absence assertion

Replace raw json.contains("defer") substring search with
ObjectNode.has("defer") to avoid false positives if another
field ever contains the substring.

Addresses review comment r3468393829.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
Co-authored-by: Ed Burns <edburns@microsoft.com>
edburns added a commit that referenced this pull request Jun 24, 2026
…ion API (#1779)

* Initial plan

* feat(java): Add ToolDefinition.fromObject() and fromClass() static methods

Adds static methods that load processor-generated $$CopilotToolMeta
classes and return List<ToolDefinition> with fully working tool
definitions (schema + invocation handlers).

- fromObject(Object): discovers tools from an instance with @copilotTool methods
- fromClass(Class<?>): discovers tools from a class with static @copilotTool methods
- Private getConfiguredMapper(): provides ObjectMapper matching JsonRpcClient config
- Throws IllegalStateException with helpful message if generated class not found
- Both methods annotated with @CopilotExperimental

Includes comprehensive test suite (ToolDefinitionFromObjectTest) covering:
- Basic discovery and schema verification
- Handler invocation for String, void, and CompletableFuture returns
- Argument coercion with primitives, String, boolean, and enums
- Default value handling when arguments are omitted
- Error case for missing generated class
- java.time argument deserialization (validates JavaTimeModule contract)
- Override tool flag propagation
- ToolDefer.NONE → null mapping (defer absent from JSON output)

Closes #1761

Co-authored-by: edburns <75821+edburns@users.noreply.github.com>

* fix: replace misleading generated-file comment in test fixtures

The $$CopilotToolMeta test fixtures are hand-written, not processor-
generated. Update the header comment to say so accurately.
Also fix Spotless formatting in CopilotToolProcessor.java.

Addresses PR review comment about test Javadoc inaccuracy.

* fix: introduce CopilotToolMetadataProvider interface to eliminate setAccessible

Replace reflective Method.invoke + setAccessible(true) in
ToolDefinition.loadDefinitions() with a typed interface cast.
Generated $$CopilotToolMeta classes now implement
CopilotToolMetadataProvider<T>, making them JPMS-safe and
removing the InaccessibleObjectException risk.

Addresses review comment r3468393716.

* fix: validate fromClass() rejects instance @copilotTool methods

fromClass() now scans for non-static @copilotTool methods and throws
IllegalArgumentException with an actionable message listing the
offending methods and directing users to fromObject() instead.
Prevents hard-to-diagnose NullPointerException at invocation time.

Addresses review comment r3468393764.

* fix: use parsed JSON tree for defer-absence assertion

Replace raw json.contains("defer") substring search with
ObjectNode.has("defer") to avoid false positives if another
field ever contains the substring.

Addresses review comment r3468393829.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
Co-authored-by: Ed Burns <edburns@microsoft.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Java] @CopilotTool ergonomics 4.4: ToolDefinition.fromObject(Object) registration API

3 participants