Java: Implement @CopilotTool ergonomics#1792
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces an ergonomic, annotation-based Java tool definition API (@CopilotTool / @Param) backed by a JSR-269 annotation processor that generates $$CopilotToolMeta companion classes, enabling ToolDefinition.fromObject(...) (and fromClass(...)) to produce runnable ToolDefinition instances with generated JSON Schemas and handlers.
Changes:
- Add
@CopilotTool/@Paramannotations andCopilotToolProcessorto generate tool metadata classes at compile time. - Add
ToolDefinition.fromObject(...)/fromClass(...)runtime entry points to load generated tool definitions. - Add unit/processor tests plus a Java E2E snapshot + integration test to validate wire-level behavior.
Show a summary per file
| File | Description |
|---|---|
| test/snapshots/tools/ergonomic_tool_definition.yaml | New replay snapshot for ergonomic tool definition scenario |
| java/src/test/java/com/github/copilot/tool/SchemaGeneratorTest.java | Compilation-testing coverage for schema generator outputs |
| java/src/test/java/com/github/copilot/tool/CopilotToolProcessorTest.java | Processor compilation tests for generated meta + validation errors |
| java/src/test/java/com/github/copilot/tool/CopilotToolAnnotationTest.java | Annotation retention/targets/defaults verification |
| java/src/test/java/com/github/copilot/rpc/ToolDefinitionFromObjectTest.java | E2E-style unit tests for ToolDefinition.fromObject(...) behavior |
| java/src/test/java/com/github/copilot/rpc/fixtures/SimpleTools$$CopilotToolMeta.java | Hand-written fixture mimicking generated meta output (simple tools) |
| java/src/test/java/com/github/copilot/rpc/fixtures/SimpleTools.java | Fixture tool class annotated with @CopilotTool / @Param |
| java/src/test/java/com/github/copilot/rpc/fixtures/OverrideTools$$CopilotToolMeta.java | Fixture meta for override flag behavior |
| java/src/test/java/com/github/copilot/rpc/fixtures/OverrideTools.java | Fixture tool class for override flag |
| java/src/test/java/com/github/copilot/rpc/fixtures/MultiReturnTools$$CopilotToolMeta.java | Fixture meta for return-type handling patterns |
| java/src/test/java/com/github/copilot/rpc/fixtures/MultiReturnTools.java | Fixture tool class for return-type handling patterns |
| java/src/test/java/com/github/copilot/rpc/fixtures/DefaultValueTools$$CopilotToolMeta.java | Fixture meta for default parameter values |
| java/src/test/java/com/github/copilot/rpc/fixtures/DefaultValueTools.java | Fixture tool class for default parameter values |
| java/src/test/java/com/github/copilot/rpc/fixtures/DateTimeTools$$CopilotToolMeta.java | Fixture meta for java.time argument conversion |
| java/src/test/java/com/github/copilot/rpc/fixtures/DateTimeTools.java | Fixture tool class using LocalDateTime parameter |
| java/src/test/java/com/github/copilot/rpc/fixtures/ArgCoercionTools$$CopilotToolMeta.java | Fixture meta for mixed-argument coercion |
| java/src/test/java/com/github/copilot/rpc/fixtures/ArgCoercionTools.java | Fixture tool class for mixed-argument coercion |
| java/src/test/java/com/github/copilot/e2e/ErgonomicToolDefinitionIT.java | Failsafe IT validating end-to-end ergonomic tool usage with snapshot |
| java/src/test/java/com/github/copilot/e2e/ErgonomicTestTools$$CopilotToolMeta.java | Hand-written E2E meta fixture mimicking processor output |
| java/src/test/java/com/github/copilot/e2e/ErgonomicTestTools.java | Tool fixture class for E2E ergonomic test |
| java/src/main/resources/META-INF/services/javax.annotation.processing.Processor | Registers CopilotToolProcessor as an annotation processor |
| java/src/main/java/module-info.java | Exports com.github.copilot.tool and provides processor in module-info |
| java/src/main/java/com/github/copilot/tool/SchemaGenerator.java | New compile-time JSON-schema source generator |
| java/src/main/java/com/github/copilot/tool/Param.java | New @Param annotation |
| java/src/main/java/com/github/copilot/tool/CopilotToolProcessor.java | New processor generating $$CopilotToolMeta classes |
| java/src/main/java/com/github/copilot/tool/CopilotToolMetadataProvider.java | Contract interface for generated/manual metadata providers |
| java/src/main/java/com/github/copilot/tool/CopilotTool.java | New @CopilotTool annotation |
| java/src/main/java/com/github/copilot/rpc/ToolDefinition.java | Adds fromObject(...) / fromClass(...) + mapper holder |
| java/src/main/java/com/github/copilot/rpc/ToolDefer.java | Adds NONE sentinel + serialization safety adjustments |
| java/mvnw | Adds Maven wrapper script under java/ |
Copilot's findings
- Files reviewed: 29/30 changed files
- Comments generated: 3
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
edburns/1682-java-tool-ergonomics-review-draft-01 |
@CopilotTool ergonomics@CopilotTool ergonomics
This comment has been minimized.
This comment has been minimized.
Your branch is up to date with 'upstream/edburns/1682-java-tool-ergonomics'. Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: 1682-java-tool-ergonomics-prompts-remove-before-merge/20260618-prompts.md Signed-off-by: Ed Burns <edburns@microsoft.com>
- Add NONE constant to ToolDefer enum for annotation default value - Create com.github.copilot.tool.CopilotTool annotation - Create com.github.copilot.tool.Param annotation - Export com.github.copilot.tool package in module-info.java - Add CopilotToolAnnotationTest verifying retention, targets, defaults Closes #1758
NONE is an annotation-only sentinel for @copilotTool(defer=...) defaults. Its @jsonvalue now returns null so @JsonInclude(NON_NULL) omits it from the JSON-RPC payload, matching the nullable/optional semantics used by all other SDKs (.NET CopilotToolDefer?, Node defer?, Go omitempty, Python | None, Rust Option<DeferMode>).
* WIP Phase 4.1 * Remove prompts, pre-merge * fix(java): correct ToolDefer.NONE Javadoc on @jsonvalue null semantics Clarify that @jsonvalue returning null does not cause field omission by @JsonInclude(NON_NULL) — it only changes the leak from "" to null. The primary protection is mapping NONE to a null field reference before constructing ToolDefinition (responsibility of the annotation processor and ToolDefinition.fromObject()). * fix(java): address three review comments Co-authored-by: edburns <75821+edburns@users.noreply.github.com> * Revert "Remove prompts, pre-merge" This reverts commit a4fe9b2. --------- Co-authored-by: Ed Burns <edburns@microsoft.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
…ity (#1766) * Initial plan * feat(java): add SchemaGenerator compile-time type-to-JSON-Schema utility Creates SchemaGenerator.java that maps javax.lang.model TypeMirror instances to JSON Schema source code literals (Map.of(...) expressions). Implements all 24 type mappings from the specification including: - Primitives and boxed types (int/Integer, long/Long, etc.) - String, UUID, OffsetDateTime - Collections (List<T>, Collection<T>, Set<T>) - Maps (Map<String, V> with typed values) - Arrays (String[]) - Enums (with constant enumeration) - Records and POJOs (with properties/required) - Optional<T>, OptionalInt, OptionalDouble - Sealed interfaces (oneOf) - JsonNode and Object (any) Also adds SchemaGeneratorTest using compilation-testing approach with javax.tools.JavaCompiler to exercise the generator at compile time. Closes #1759 * fix: address code review - remove unused param, handle all primitive types * fix(java): correct SimpleJavaFileObject override - getCharContent not getContent Co-authored-by: edburns <75821+edburns@users.noreply.github.com> * spotless * Remove .class files generated by test * spotless * fix: use Map.ofEntries for properties to avoid Map.of 10-entry limit Address review comment r3461777483: Map.of() only supports up to 10 key-value pairs. Switch properties maps in SchemaGenerator to use Map.ofEntries(Map.entry(...), ...) so records/POJOs/methods with >10 fields won't cause generated source compilation failures. Update SchemaGeneratorTest expectations to match the new format. * fix: add missing Byte/Short/Character boxed type mappings Address review comment r3461777428: Byte and Short now map to "integer", Character maps to "string", matching their primitive equivalents. Add tests for all three. * fix: add missing OptionalLong mapping in generateDeclaredTypeSchema Address review comment r3461777459: OptionalLong was handled in isOptionalType/unwrapOptional but missing from generateDeclaredTypeSchema, causing it to fall through to POJO introspection when used as a direct return type. Add the mapping and tests for OptionalInt, OptionalLong, and OptionalDouble. * fix: correct misleading @JsonSubTypes comment on sealed interface handling Address review comment r3461777579: the implementation uses getPermittedSubclasses() (Java sealed types), not Jackson annotations. * test: add sealed interface test for oneOf schema generation Address review comment r3461777685: the processor had special handling for TestSealed* types but no test exercised generateSealedSchema(). Add a test with a sealed interface (TestSealedShape) and two record permits (Circle, Rect) verifying the oneOf schema output. * test: add >10-field record test proving Map.ofEntries compiles Address review comment r3461777706: add a test with an 11-component record that verifies the generated Map.ofEntries(...) expression actually compiles, proving the Map.of 10-entry limit fix works end-to-end. --------- 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>
…1777) * Resume 1682 iterating * Phase 03 answer questions * On branch edburns/1682-java-tool-ergonomics Your branch is up to date with 'upstream/edburns/1682-java-tool-ergonomics'. Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: 1682-java-tool-ergonomics-prompts-remove-before-merge/20260618-prompts.md Signed-off-by: Ed Burns <edburns@microsoft.com> * WIP: Phase 3. Question 3.4 * WIP: Phase 3. Question 3.6 * WIP: Phase 3. Question 3.6: Answer * Answer 3.7 * Resolve 3.8 * Initial plan * feat(java): create @copilotTool and @Param annotations with tests - Add NONE constant to ToolDefer enum for annotation default value - Create com.github.copilot.tool.CopilotTool annotation - Create com.github.copilot.tool.Param annotation - Export com.github.copilot.tool package in module-info.java - Add CopilotToolAnnotationTest verifying retention, targets, defaults Closes #1758 * spotless * fix(java): make ToolDefer.NONE serialize as null to prevent wire leak NONE is an annotation-only sentinel for @copilotTool(defer=...) defaults. Its @jsonvalue now returns null so @JsonInclude(NON_NULL) omits it from the JSON-RPC payload, matching the nullable/optional semantics used by all other SDKs (.NET CopilotToolDefer?, Node defer?, Go omitempty, Python | None, Rust Option<DeferMode>). * WIP Phase 4.1 * feat(java): create @copilotTool and @Param annotations (#1763) * WIP Phase 4.1 * Remove prompts, pre-merge * fix(java): correct ToolDefer.NONE Javadoc on @jsonvalue null semantics Clarify that @jsonvalue returning null does not cause field omission by @JsonInclude(NON_NULL) — it only changes the leak from "" to null. The primary protection is mapping NONE to a null field reference before constructing ToolDefinition (responsibility of the annotation processor and ToolDefinition.fromObject()). * fix(java): address three review comments Co-authored-by: edburns <75821+edburns@users.noreply.github.com> * Revert "Remove prompts, pre-merge" This reverts commit a4fe9b2. --------- Co-authored-by: Ed Burns <edburns@microsoft.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: edburns <75821+edburns@users.noreply.github.com> * Initial plan * feat(java): add SchemaGenerator compile-time type-to-JSON-Schema utility (#1766) * Initial plan * feat(java): add SchemaGenerator compile-time type-to-JSON-Schema utility Creates SchemaGenerator.java that maps javax.lang.model TypeMirror instances to JSON Schema source code literals (Map.of(...) expressions). Implements all 24 type mappings from the specification including: - Primitives and boxed types (int/Integer, long/Long, etc.) - String, UUID, OffsetDateTime - Collections (List<T>, Collection<T>, Set<T>) - Maps (Map<String, V> with typed values) - Arrays (String[]) - Enums (with constant enumeration) - Records and POJOs (with properties/required) - Optional<T>, OptionalInt, OptionalDouble - Sealed interfaces (oneOf) - JsonNode and Object (any) Also adds SchemaGeneratorTest using compilation-testing approach with javax.tools.JavaCompiler to exercise the generator at compile time. Closes #1759 * fix: address code review - remove unused param, handle all primitive types * fix(java): correct SimpleJavaFileObject override - getCharContent not getContent Co-authored-by: edburns <75821+edburns@users.noreply.github.com> * spotless * Remove .class files generated by test * spotless * fix: use Map.ofEntries for properties to avoid Map.of 10-entry limit Address review comment r3461777483: Map.of() only supports up to 10 key-value pairs. Switch properties maps in SchemaGenerator to use Map.ofEntries(Map.entry(...), ...) so records/POJOs/methods with >10 fields won't cause generated source compilation failures. Update SchemaGeneratorTest expectations to match the new format. * fix: add missing Byte/Short/Character boxed type mappings Address review comment r3461777428: Byte and Short now map to "integer", Character maps to "string", matching their primitive equivalents. Add tests for all three. * fix: add missing OptionalLong mapping in generateDeclaredTypeSchema Address review comment r3461777459: OptionalLong was handled in isOptionalType/unwrapOptional but missing from generateDeclaredTypeSchema, causing it to fall through to POJO introspection when used as a direct return type. Add the mapping and tests for OptionalInt, OptionalLong, and OptionalDouble. * fix: correct misleading @JsonSubTypes comment on sealed interface handling Address review comment r3461777579: the implementation uses getPermittedSubclasses() (Java sealed types), not Jackson annotations. * test: add sealed interface test for oneOf schema generation Address review comment r3461777685: the processor had special handling for TestSealed* types but no test exercised generateSealedSchema(). Add a test with a sealed interface (TestSealedShape) and two record permits (Circle, Rect) verifying the oneOf schema output. * test: add >10-field record test proving Map.ofEntries compiles Address review comment r3461777706: add a test with an 11-component record that verifies the generated Map.ofEntries(...) expression actually compiles, proving the Map.of 10-entry limit fix works end-to-end. --------- 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> * WIP 4.3 * Initial plan * feat(java): Add CopilotToolProcessor annotation processor (task 4.3) Implements JSR 269 annotation processor that finds @CopilotTool-annotated methods and generates $$CopilotToolMeta companion classes containing tool definitions, JSON Schema, and invocation lambdas. Key features: - snake_case tool name conversion from camelCase method names - Access level enforcement (compile error for private methods) - Return type handling (String, void, CompletableFuture<String>, etc.) - Argument deserialization (direct cast for primitives/String, convertValue for complex) - @Param description and defaultValue support in schema - ToolDefer support (NONE maps to null/regular create) - overridesBuiltInTool and skipPermission support Also includes comprehensive test suite using javax.tools.JavaCompiler programmatic compilation. Closes #1760 Co-authored-by: edburns <75821+edburns@users.noreply.github.com> * fix: Address code review feedback - Use fully qualified type names in generated code for type safety - Fix Files.walk() resource leak in test with try-with-resources - Rename exception variables for clarity Co-authored-by: edburns <75821+edburns@users.noreply.github.com> * fix: Fix Spotless formatting and test classpath for JDK 17 - Remove unused Collections import - Reformat boolean expressions: && at start of continuation lines - Reformat ternary: ? at start of continuation line - Reformat .replace() chain with one call per line - Fix hasErrorContaining stream method chain formatting - Fix resolveClasspath() to use System.getProperty("java.class.path") first, ensuring Jackson and all test deps are available when compiling generated $$CopilotToolMeta code * fix: Fix remaining Spotless violations and test classpath resolution - Merge propertyEntries.add() onto one line per formatter requirement - Fix sb.append() chain formatting to match Eclipse formatter output - Revert escapeJava to original line-breaking style (formatter preference) - Fix resolveClasspath() to combine system classpath with CodeSource paths from key classes (SDK, Jackson, RPC types) ensuring all dependencies are available for javac in the annotation processor test * fix: Add jackson-core and jackson-annotations to test classpath The generated 6342CopilotToolMeta code uses ObjectMapper which requires jackson-core (Versioned, JsonFactory) and jackson-annotations at compile time. Add these transitive dependencies to the key classes list so their CodeSource paths are included in the javac classpath. * fix: Fix Spotless formatting for keyClasses array initializer * fix(java): Pass ObjectMapper as parameter in generated $$CopilotToolMeta contract Address PR #1777 review comment (r3463252393): the generated $$CopilotToolMeta class was using `new ObjectMapper()`, which lacks the SDK Jackson configuration (JavaTimeModule, NON_NULL inclusion, lenient unknown-properties). This would break tool argument coercion and return serialization at runtime for java.time.* and other types. Instead of embedding a bare or configured ObjectMapper in the generated code, change the generated `definitions()` method signature from: definitions(MyTools instance) to: definitions(MyTools instance, ObjectMapper mapper) This establishes an internal contract: the caller (the future ToolDefinition.fromObject() in issue #1761) is responsible for supplying a properly configured mapper via reflective invocation. The generated code uses `mapper` for all convertValue() and writeValueAsString() calls. Benefits: - No DRY violation (mapper config stays canonical in JsonRpcClient) - No new public API exposing ObjectMapper - No package-visibility workarounds - Clean separation: generated code declares its needs, caller supplies Issue #1761 description has been updated to document this contract so the implementing agent knows to pass ObjectMapper as the second argument when reflectively invoking definitions(). * fix(java): restrict single-param shortcut to records only Address review comment on PR #1777: the isRecordOrPojo heuristic incorrectly triggered for JDK container types (List, Map, etc.) when used as a single tool parameter. For example, a tool with parameter List<String> would attempt to deserialize the entire arguments object as a List, failing at runtime. Replace the heuristic with a deterministic check: only Java records qualify for the getArgumentsAs() shortcut. Records are immutable data carriers with compiler-guaranteed component lists, making them safe for whole-object deserialization. POJOs and all other class types now fall through to the per-field extraction path, which always works correctly. Removed isSimpleType() helper which was only used by the old heuristic. * fix(java): emit typed default values in JSON Schema Address review comment on PR #1777: @Param(defaultValue=...) was always emitted as a JSON string in the generated schema's 'default' field, making numeric and boolean defaults the wrong type (e.g., "10" instead of 10, "true" instead of true). Changes: - withMeta helper: String defaultValue -> Object defaultValue - buildPropertySchema: reuse generateDefaultLiteral() to emit typed Java literals (int, boolean, etc.) instead of always quoting - Add test emitsTypedDefaultValuesInSchema verifying int -> 10, boolean -> true, String -> "hello" in generated code * fix(java): fix double 61059CopilotToolMeta suffix in test helper Address review comment on PR #1777: getGeneratedSource() fallback search appended 61059CopilotToolMeta to a simpleName that already contained it, producing MyTools$$CopilotToolMeta$$CopilotToolMeta. Simplify to just match on 'class <simpleName>'. * fix(java): use record constructor for independent flag combination Address SDK Consistency Review on PR #1777: the if/else if chain in writeToolDefinition silently dropped combined annotation flags (e.g., overridesBuiltInTool + skipPermission + defer). All other SDKs support combining these flags simultaneously. Replace the factory method dispatch with a direct call to the ToolDefinition record constructor, which accepts all seven fields independently. Each flag is now emitted as its own argument: Boolean.TRUE or null for overridesBuiltInTool/skipPermission, ToolDefer.X or null for defer. Add test generatesCombinedFlags verifying all three flags appear in generated code when set together. --------- Signed-off-by: Ed Burns <edburns@microsoft.com> Co-authored-by: Ed Burns <edburns@microsoft.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
- LocalDateTime, Instant, ZonedDateTime → format: date-time - LocalDate → format: date - LocalTime → format: time These hints tell the LLM what string format to produce for date/time parameters. Previously only OffsetDateTime was mapped. Adds SchemaGeneratorTest cases for each new type mapping.
The processor now generates null-check + wrapping code for Optional, OptionalInt, OptionalLong, and OptionalDouble parameters instead of incorrectly calling mapper.convertValue(..., Optional.class). For Optional<T>, extracts the inner value using type-appropriate coercion then wraps with Optional.of()/Optional.empty(). For OptionalInt/Long/Double, uses the primitive Number extraction then wraps with the corresponding OptionalX.of()/empty(). Adds CopilotToolProcessorTest for generated code verification and ToolDefinitionFromObjectTest for end-to-end handler invocation with both present and absent optional values.
…#1799) * Fix Java tool-processor test generation and stabilize session-id test Address the Java test failures observed in the Java 17 surefire/failsafe run by fixing how annotation-processing output is discovered in CopilotToolProcessor tests and by hardening one timing-sensitive session test. Changes included: - CopilotToolProcessor: resolve @copilotTool elements via TypeElement lookup and reuse that element list through validation and generation passes, making annotation discovery robust across compiler/module contexts. - CopilotToolProcessorTest: force annotation processing in the in-memory compile harness (-proc:full, explicit processor), close the file manager with try-with-resources, and add a collecting forwarding file manager that captures generated source content from getJavaFileForOutput to avoid missing generated CopilotToolMeta classes in tests. - CopilotSessionTest#testShouldGetLastSessionId: add bounded retry for session creation (including timeout and execution-timeout-cause handling) to absorb transient startup delays while preserving failure behavior on persistent errors. Result: - CopilotToolProcessorTest now consistently observes generated CopilotToolMeta output and passes. - The full requested Maven workflow (jacoco prepare/report + surefire + failsafe under Java 17, with prior Java 25 compile) completes successfully. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Spotless * Avoid leaking session. The retry on session creation uses `future.get(timeout)` but does not cancel the in-flight `createSession` future when a timeout occurs. If attempt 1 eventually completes after attempt 2 starts, it can leave an orphaned session registered in the client (and potentially race `getLastSessionId` persistence), reintroducing flakiness and leaking resources. Capture the future and cancel it on timeout before retrying. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Add abort-session snapshot variant for interrupted tool calls Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
ebff5aa to
60eb094
Compare
This comment has been minimized.
This comment has been minimized.
modified: java/src/main/java/com/github/copilot/tool/CopilotToolProcessor.java modified: java/src/main/java/com/github/copilot/tool/Param.java modified: java/src/main/java/com/github/copilot/tool/SchemaGenerator.java - Add `CopilotExperimental` more liberally
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
c58fd1e to
a3f50c3
Compare
Cross-SDK Consistency Review ✅This PR adds the ergonomic
Cross-cutting options are consistent ✅The new
|
|
PR 1792 Reviewer’s Guide — Java
@CopilotToolErgonomicsPR: #1792
Epic: #1682
ADR:
java/docs/adr/adr-005-tool-definition.mdThis PR introduces the Java high-level tool API (
@CopilotTool+@Param) and compile-time metadata generation, while keeping low-levelToolDefinition.create(...)as the foundation.What to optimize for in review
Please focus on API semantics, generated-invocation correctness, and schema fidelity rather than style. The main question is whether this design is robust enough for long-term Java SDK ergonomics and interoperability.
Scope mapped to epic child issues
@CopilotTooland@ParamannotationsSchemaGenerator(TypeMirror-> JSON Schema source)CopilotToolProcessorgenerating$$CopilotToolMetaToolDefinition.fromObject(...)/fromClass(...)registration bridgeCommit stream reading order (recommended)
Ignore WIP/prompt commits and review in this logical order:
feat(java): create @CopilotTool and @Param annotations (#1763)feat(java): add SchemaGenerator compile-time type-to-JSON-Schema utility (#1766)feat(java): Add CopilotToolProcessor annotation processor (task 4.3) (#1777)feat(java): Add ToolDefinition.fromObject() and fromClass() registration API (#1779)Add E2E integration test for ergonomic @CopilotTool + ToolDefinition.fromObject() API (#1787)File-level review map
java/src/main/java/com/github/copilot/tool/CopilotTool.java,java/src/main/java/com/github/copilot/tool/Param.java,java/src/main/java/com/github/copilot/rpc/ToolDefinition.javajava/src/main/java/com/github/copilot/tool/CopilotToolMetadataProvider.javajava/src/main/java/com/github/copilot/tool/CopilotToolProcessor.java,java/src/main/resources/META-INF/services/javax.annotation.processing.Processor,java/src/main/java/module-info.javajava/src/main/java/com/github/copilot/tool/SchemaGenerator.javajava/src/test/java/com/github/copilot/tool/CopilotToolProcessorTest.java,java/src/test/java/com/github/copilot/tool/SchemaGeneratorTest.javajava/src/test/java/com/github/copilot/rpc/ToolDefinitionFromObjectTest.java+ fixtures underjava/src/test/java/com/github/copilot/rpc/fixtures/java/src/test/java/com/github/copilot/e2e/ErgonomicToolDefinitionIT.java,test/snapshots/tools/ergonomic_tool_definition.yamlArchitectural intent (as implemented)
ToolDefinition.fromObject/fromClassloads generated$$CopilotToolMeta.IllegalStateException.ToolDefinition.create*remains available.High-value review questions (actionable feedback requested)
@Retention(RUNTIME)on@CopilotTool/@Paramstill the right long-term choice now thatfromObject()is processor-only by default (no runtime fallback path)?required=true+defaultValueis compile-time errorStringpassthroughvoid -> "Success"CompletableFuture<String>passthrough (object-cast)RuntimeExceptionwrapping during serialization)?SchemaGeneratorcoverage sufficient for v1 (primitives, boxed, collections, maps, records/classes, enums, sealed interfaces, java.time formats, Optional variants)?Param.name()interaction with generated extraction/schema intuitive and predictable for advanced use?fromClass) now robust enough after qualified-class-name and instance-method guard fixes?ToolDefer.NONE -> nullwire behavior the right compatibility choice?Deliberate non-goals / constraints in this PR
fromObject()when metadata is missing (fail fast instead).@CopilotExperimental.Extra context from iteration artifacts
The prompt/design trail in
copilot-sdk-01/1682-java-tool-ergonomics-prompts-remove-before-mergetracks the ignorance-reduction phase and key decisions that shaped this PR (especially annotation shape, default semantics, processor-only direction, and schema coverage expansions).If you see a mismatch between ADR intent and implementation behavior, that is exactly the class of feedback I want.
Note on
CopilotToolMetadataProviderCopilotToolMetadataProvider<T>is the runtime seam between generated metadata andToolDefinition.fromObject(...)loading.The processor emits
$$CopilotToolMetaclasses that implement this interface and expose:This keeps
fromObject/fromClassdecoupled from generated-class internals, and also allows advanced/manual implementations of the provider contract when needed.Usage sample snippets (from
copilot-sdk-01/.../simple-weather-demo)1.
pom.xml— experimental + annotation processor2. Tool declaration with
@CopilotTool3. Registration via
ToolDefinition.fromObject(...)