Skip to content

[Java] @CopilotTool ergonomics: Support hidden ToolInvocation parameter injection #1831

Description

@edburns

Overview

Add support for hidden ToolInvocation parameter injection in the annotation-based Java tools API so a @CopilotTool method can opt in to receiving invocation context without exposing that parameter in the tool schema.

Example target usage:

@CopilotTool("Reports progress for the current tool call")
public String reportProgress(
        @Param("Current phase") String phase,
        ToolInvocation invocation) {
    return "phase=" + phase + ", session=" + invocation.getSessionId();
}

ToolInvocation must be treated as runtime context, not as an LLM-visible parameter.

Parent Epic: #1682
Parent Follow-up: #1809

Motivation

The current annotation-based Java API generates a ToolDefinition handler that receives a ToolInvocation, but @CopilotTool methods themselves cannot declare ToolInvocation as a hidden injected parameter. Every declared method parameter is currently treated as schema/input data.

This is a capability gap relative to:

  • the low-level Java ToolHandler API, which already exposes ToolInvocation
  • the .NET API, which supports hidden ToolInvocation binding in CopilotTool.DefineTool(...)

Some advanced tools need access to invocation context such as:

  1. sessionId
  2. toolCallId
  3. toolName
  4. raw arguments for logging, auditing, or custom handling

Without this feature, users must drop down to the low-level ToolDefinition.create(...) API just to access invocation context.

Deliverables

Files to modify

  1. java/src/main/java/com/github/copilot/tool/CopilotToolProcessor.java
  2. java/src/test/java/com/github/copilot/rpc/ToolDefinitionFromObjectTest.java
  3. java/src/test/java/com/github/copilot/rpc/fixtures/* — add or update fixtures that exercise ToolInvocation injection
  4. java/README.md and/or the relevant Java tools documentation if annotation-based examples are documented there

Implementation specification

1. Treat ToolInvocation as a hidden special parameter

If a @CopilotTool method declares a parameter of type com.github.copilot.rpc.ToolInvocation, the annotation processor must:

  • exclude that parameter from generated JSON Schema
  • exclude that parameter from required
  • not read that parameter from invocation.getArguments()
  • pass the current invocation object directly when generating the method call

This is opt-in by signature only. Methods that do not declare ToolInvocation must behave exactly as they do today.

2. Schema generation changes

In generateSchemaWithParamMetadata(...), skip parameters whose erased/qualified type is com.github.copilot.rpc.ToolInvocation.

That means this method:

@CopilotTool("Reports progress")
public String reportProgress(@Param("Current phase") String phase,
        ToolInvocation invocation) {
    return "ok";
}

must produce a schema equivalent to a single-parameter tool with only phase.

3. Invocation lambda generation changes

In generateLambdaBody(...) and argument-list generation, treat ToolInvocation specially:

  • normal parameters are still extracted from Map<String, Object> args = invocation.getArguments();
  • ToolInvocation parameters are bound directly to the in-scope invocation variable

Generated code should conceptually behave like:

invocation -> {
    Map<String, Object> args = invocation.getArguments();
    String phase = (String) args.get("phase");
    return CompletableFuture.completedFuture(instance.reportProgress(phase, invocation));
}

4. Validation rules

The processor should reject ambiguous or unsupported forms with compile-time errors.

At minimum:

  1. More than one ToolInvocation parameter on the same method is an error.
  2. ToolInvocation must not be annotated with @Param.
  3. ToolInvocation must not be used as the single-record wrapper parameter shortcut.

The error messages should clearly explain that ToolInvocation is injected runtime context and not part of the tool schema.

5. Static methods

This feature must work for both instance and static @CopilotTool methods.

6. Return-type behavior remains unchanged

This issue is only about parameter binding. Existing return handling must continue to work unchanged for:

  • String
  • void
  • non-String sync return values
  • CompletableFuture<T>

Gating tests and criteria

All of the following must pass:

Unit/integration tests

  1. Instance method injection: @CopilotTool instance method with one normal argument plus ToolInvocation receives both values correctly.
  2. Static method injection: static @CopilotTool method with ToolInvocation works correctly.
  3. Schema exclusion: generated tool schema does not include ToolInvocation as a property or required parameter.
  4. No-regression path: existing annotation-based tools without ToolInvocation continue to behave unchanged.
  5. Async method compatibility: a @CopilotTool method returning CompletableFuture<String> can also declare ToolInvocation.
  6. Compile error on duplicate context params: two ToolInvocation parameters produce a processor error.
  7. Compile error on @Param ToolInvocation: annotating a ToolInvocation parameter with @Param produces a processor error.

Full build

  1. cd java && mvn verify passes with no regressions.

Design decisions

  • Opt-in by signature. Users only receive ToolInvocation if they explicitly declare it.
  • Hidden runtime context. ToolInvocation is never part of the LLM-visible schema.
  • Annotation API parity improvement. This closes a gap between @CopilotTool and the low-level ToolHandler/ToolDefinition.create(...) path.
  • No CDI or JSR-330 dependency. This feature is simple generated-parameter binding, not container-managed injection.

Example usage

class ProgressTools {

    @CopilotTool("Reports the current phase and session")
    public String reportProgress(
            @Param("Current phase") String phase,
            ToolInvocation invocation) {
        return "phase=" + phase
                + ", sessionId=" + invocation.getSessionId()
                + ", toolCallId=" + invocation.getToolCallId();
    }
}

Branch and PR conventions

  • Branch: create from main on upstream
  • PR target: main
  • Run cd java && mvn verify before merging
  • Follow existing Javadoc conventions for any public API/documentation updates

Metadata

Metadata

Labels

enhancementjavaPull requests that update java code

Type

No fields configured for Task.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions