Skip to content

[Java] Support hidden ToolInvocation injection in @CopilotTool methods#1832

Merged
edburns merged 5 commits into
mainfrom
copilot/add-hidden-toolinvocation-support
Jun 29, 2026
Merged

[Java] Support hidden ToolInvocation injection in @CopilotTool methods#1832
edburns merged 5 commits into
mainfrom
copilot/add-hidden-toolinvocation-support

Conversation

Copilot AI commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Fixes #1831

This change closes the ergonomics gap in Java’s annotation-based tools API by allowing @CopilotTool methods to declare ToolInvocation as injected runtime context. ToolInvocation is now treated as non-schema context (not LLM-visible input), while existing parameter binding and return-type behavior remain unchanged.

  • Processor: hidden context parameter handling

    • CopilotToolProcessor now treats com.github.copilot.rpc.ToolInvocation as a special injected parameter.
    • Excludes ToolInvocation from generated properties and required.
    • Does not read ToolInvocation from invocation.getArguments().
    • Binds ToolInvocation parameters directly to the in-scope invocation object when generating method calls.
    • Applies for both instance and static tool methods.
  • Processor: compile-time validation

    • Emits an error if a method declares more than one ToolInvocation parameter.
    • Emits an error if ToolInvocation is annotated with @Param.
    • Keeps single-record wrapper behavior scoped to schema-visible parameters only, preventing ToolInvocation from participating in that shortcut path.
  • Tests and fixtures

    • Added/updated processor tests for:
      • schema exclusion
      • instance/static/async invocation wiring
      • duplicate ToolInvocation compile error
      • @Param ToolInvocation compile error
    • Added fixture-based ToolDefinition.fromObject/fromClass coverage to verify runtime behavior and schema output for injected ToolInvocation.
  • Java docs

    • Added README documentation and example showing annotation-based ToolInvocation injection semantics.

Example (now supported):

@CopilotTool("Reports progress for the current tool call")
public String reportProgress(
        @Param("Current phase") String phase,
        ToolInvocation invocation) {
    return "phase=" + phase + ", session=" + invocation.getSessionId();
}
Copilot AI and others added 2 commits June 29, 2026 20:26
Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
Copilot AI changed the title [WIP] Add support for hidden ToolInvocation parameter injection Jun 29, 2026
Copilot AI requested a review from edburns June 29, 2026 20:38
@github-actions

This comment has been minimized.

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

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 support in the Java annotation-processor-based tools API for injecting com.github.copilot.rpc.ToolInvocation as hidden runtime context in @CopilotTool methods, ensuring it is not exposed as part of the tool’s JSON schema while still being passed to the user method at invocation time.

Changes:

  • Updated CopilotToolProcessor to recognize ToolInvocation parameters as injected context: excluded from schema generation/required list, not read from invocation.getArguments(), and wired into generated method calls.
  • Added compile-time validation for invalid ToolInvocation usage (duplicate parameters, or @Param applied to ToolInvocation).
  • Added processor tests and runtime ToolDefinition.fromObject/fromClass fixture-based tests to verify schema output and invocation behavior; updated Java README docs with usage guidance.
Show a summary per file
File Description
java/src/main/java/com/github/copilot/tool/CopilotToolProcessor.java Implements ToolInvocation as injected (non-schema) context parameter; adds validation and updates schema/lambda generation accordingly.
java/src/test/java/com/github/copilot/tool/CopilotToolProcessorTest.java Adds processor-level tests covering schema exclusion, method-call wiring (instance/static/async), and validation errors.
java/src/test/java/com/github/copilot/rpc/ToolDefinitionFromObjectTest.java Adds runtime tests ensuring ToolDefinition.fromObject/fromClass correctly injects ToolInvocation and that schema excludes it.
java/src/test/java/com/github/copilot/rpc/fixtures/InvocationAwareTools.java Adds instance/async tool fixtures that consume injected ToolInvocation.
java/src/test/java/com/github/copilot/rpc/fixtures/InvocationAwareTools$$CopilotToolMeta.java Hand-written metadata fixture mirroring expected processor output for injected ToolInvocation (instance + async).
java/src/test/java/com/github/copilot/rpc/fixtures/StaticInvocationTools.java Adds static tool fixture that consumes injected ToolInvocation.
java/src/test/java/com/github/copilot/rpc/fixtures/StaticInvocationTools$$CopilotToolMeta.java Hand-written metadata fixture mirroring expected processor output for injected ToolInvocation (static).
java/README.md Documents annotation-based ToolInvocation injection semantics with an example.

Review details

  • Files reviewed: 8/8 changed files
  • Comments generated: 0
  • Review effort level: Low

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.

Review details

  • Files reviewed: 8/8 changed files
  • Comments generated: 1
  • Review effort level: Low
@github-actions

This comment has been minimized.

@edburns edburns left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@copilot

Overall: solid, correct implementation. The PR cleanly implements ToolInvocation injection by:

  1. Filtering it from schema generation
  2. Passing invocation directly in generated method calls
  3. Good compile-time validation (duplicate, @Param-annotated)

Test coverage assessment:

✅ Well covered:

  • Instance method + ToolInvocation (handler invocation + schema exclusion)
  • Static method + ToolInvocation
  • Async method + ToolInvocation
  • Compile error: duplicate ToolInvocation params
  • Compile error: @Param on ToolInvocation
  • Processor-level tests verify generated code shape

FIX THESE GAPS

  1. Zero visible args + ToolInvocation only. No test covers:
@CopilotTool("...")
public String onlyInvocation(ToolInvocation invocation) { ... }

This exercises the schemaParameters.isEmpty() path with a non-empty params list.

  1. ToolInvocation as FIRST parameter. All fixtures put it last. Add a test with:
@CopilotTool("...")
public String invertedOrder(ToolInvocation invocation, @Param("Phase") String phase) { ... }

The generateArgList iterates over all params (not just schema params), so position matters.

  1. Single-record + ToolInvocation. What happens with:
@CopilotTool("...")
public String withRecord(SearchArgs args, ToolInvocation invocation) { ... }

The schemaParameters.size() == 1 && isRecord(...) path would activate — does the generated code correctly pass both?

  1. Test coverage for position independence.

    1. Add Javadoc to the ToolInvocation class (or to @CopilotTool annotation Javadoc) stating that ToolInvocation may appear at any position in the parameter list and is always excluded from the tool schema regardless of position.

    2. Update the Java README ToolInvocation section to mention that position does not matter — it can appear before, between, or after @Param-annotated parameters.

    3. Add tests proving position independence:

      • ToolInvocation as the first parameter:

        @CopilotTool("...")
        public String report(ToolInvocation invocation, @Param("Phase") String phase) { ... }
        
      • ToolInvocation as the only parameter (zero visible args):

        @CopilotTool("...")
        public String onlyContext(ToolInvocation invocation) { ... }
        
      • ToolInvocation in the middle of two visible params:

        @CopilotTool("...")
        public String report(@Param("Phase") String phase, ToolInvocation invocation, @Param("Limit") int limit) { ... }
        

      Each test should verify both that the schema excludes ToolInvocation and that the handler receives the correct invocation context.

Minor correctness concern:

In generateLambdaBody, when schemaParameters is non-empty, the code extracts args from the map. But generateArgList is called with the full params list (not schemaParameters). This is correct! But the variable extraction loop only iterates schemaParameters, so ToolInvocation params won't have a local variable declared — they get invocation directly in the call. This works because the lambda parameter is named invocation. ✅

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

Copilot AI commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

@copilot

Overall: solid, correct implementation. The PR cleanly implements ToolInvocation injection by:...

Addressed in commit ea6656b.

Added coverage for all requested gaps:

  • ToolInvocation as only parameter (zero visible args)
  • ToolInvocation as first parameter
  • ToolInvocation in the middle of visible params
  • single-record schema parameter + ToolInvocation

This includes both processor-level generated-shape tests and runtime fixture-based ToolDefinition.fromObject tests verifying schema exclusion and correct invocation context wiring. I also updated ToolInvocation Javadoc and java/README.md to state that ToolInvocation can appear in any parameter position and is always excluded from schema.

Copilot AI requested a review from edburns June 29, 2026 23:10
@github-actions

Copy link
Copy Markdown
Contributor

✅ Cross-SDK Consistency Review

This PR closes a genuine consistency gap in the Java annotation-based tool API rather than introducing one. Here's how ToolInvocation access compares across all six SDKs after this PR:

SDK Pattern Status
Node.js ToolHandler<T> = (args: T, invocation: ToolInvocation) => ... ✅ Always available in handler
Python define_tool(handler=fn(params, invocation: ToolInvocation)) — optional by signature ✅ Available
Go DefineTool(name, desc, func(T, ToolInvocation) (U, error)) — always required ✅ Available
.NET CopilotTool.DefineTool binds ToolInvocation param + excludes from schema ✅ Available
Rust define_tool(name, desc, fn(ToolInvocation, P)) — always required ✅ Available
Java @CopilotTool (this PR) Injects ToolInvocation when declared as parameter, excludes from schema ✅ Now consistent

The injection semantics in this PR (parameter declared by type, excluded from LLM schema, validated at compile time) closely mirror the patterns already established in .NET (ApplyToolInvocationBinding) and Python (signature inspection). No changes are needed in any other SDK — all already support ToolInvocation access in tool handlers.

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

@edburns edburns merged commit c90f47b into main Jun 29, 2026
23 checks passed
@edburns edburns deleted the copilot/add-hidden-toolinvocation-support branch June 29, 2026 23:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants