feat(java): Add ToolDefinition.fromObject() and fromClass() registration API#1779
Conversation
…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>
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
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)andToolDefinition.fromClass(Class<?>)plus reflective loading of$$CopilotToolMeta.definitions(...)with an SDK-configuredObjectMapper. - Added a comprehensive
ToolDefinitionFromObjectTestcovering discovery, schema, handler invocation patterns, argument coercion, default values, override flag, andjava.timedeserialization behavior. - Added multiple
@CopilotToolfixture classes and corresponding$$CopilotToolMetafixtures used by the new tests.
Show a summary per file
Copilot's findings
- Files reviewed: 14/14 changed files
- Comments generated: 4
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.
Cross-SDK Consistency Review ✅All 16 changed files in this PR are Java-only ( Tool registration approaches across SDKsRationaleThe annotation-processor pattern (
The
|
…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>
…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>
…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>

Fixes #1761 .
Adds the runtime registration API that loads processor-generated
$$CopilotToolMetacompanion classes and returnsList<ToolDefinition>with fully working handlers. This is task 4.4 in the tool ergonomics epic.Changes
ToolDefinition.java— two new@CopilotExperimentalstatic methods:fromObject(Object)— for instance methods annotated with@CopilotToolfromClass(Class<?>)— for static@CopilotToolmethods (passesnullinstance)loadDefinitions()usesClass.forNamewith the target classloader,setAccessible(true), and passes a configuredObjectMapperConfiguredMapperHolder— lazy-init mapper matchingJsonRpcClient.createObjectMapper()(JavaTimeModule, lenient unknown-props, ISO dates, NON_NULL)IllegalStateExceptionwith actionable message if$$CopilotToolMetanot found — no reflection fallbackToolDefinitionFromObjectTest.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.timedeserialization via ObjectMapper contract, override flag, andToolDefer.NONE→nullJSON omissionUsage