Add function invocation middleware with call identity - #1093
Quim Muntal (qmuntal) merged 9 commits into
Conversation
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.
|
Quim Muntal (@qmuntal) could you take a look at the API when you have time? I kept arguments as raw JSON to match |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
pypi.org
To allow these domains, add them to the network.allowed list in your workflow frontmatter:
network:
allowed:
- defaults
- "pypi.org"See Network Configuration for more information.
Generated by Go API Consistency Review Agent · copilot · auto · 133 AIC · ⌖ 6.38 AIC · ⊞ 9.2K
This comment has been minimized.
This comment has been minimized.
Quim Muntal (qmuntal)
left a comment
There was a problem hiding this comment.
Like the overall approach 😄
| // Multiple callbacks execute in registration order, with the first outermost. | ||
| // Each call gets its own FunctionInvocationContext; callbacks must synchronize | ||
| // shared application state if tools can execute concurrently. | ||
| type FunctionInvocationMiddleware func(ctx context.Context, invocation *FunctionInvocationContext, next FunctionInvocationFunc) (any, error) |
There was a problem hiding this comment.
next should be the first parameter, matching Middleware ordering.
…into feat/tool-invocation-identity
|
Thanks for the review! Updated the API and moved the internal helpers into one package. Also merged latest main. |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The context-provider middleware test currently registers middleware too early to wrap the provider-added tool, causing the expected callback order to fail.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
What changed in this PR
Adds function-invocation middleware with call-ID propagation, argument/result interception, approval compatibility, and support for additional tools.
Changes:
- Adds invocation context and composable middleware.
- Propagates call IDs through tool execution.
- Expands coverage for concurrency, approvals, and additional tools.
| File | Summary |
|---|---|
internal/toolmiddleware/wrapper.go |
Internal tool wrappers and call-ID utilities. |
agent/middleware.go |
Invocation context and middleware implementation. |
agent/harness/toolautocall/autocall.go |
Applies wrappers and propagates call IDs. |
agent/harness/toolautocall/autocall_test.go |
Tests identity, concurrency, errors, and additional tools. |
agent/harness/toolautocall/autocall_approval_test.go |
Tests approval-flow identity handling. |
agent/agent_test.go |
Composition tests require middleware registration through ProviderConfig.Middlewares for context-provider tools. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Quim Muntal (qmuntal)
left a comment
There was a problem hiding this comment.
-
Context-provider tools bypass middleware with the standard constructors.
Config.Middlewaresrunsagent.go:94-95, but thechat.go:80-98doesn’t exposeProviderConfig.Middlewares, where the PR recommends registering it.Reproduced with
NewChatCompletionsAgent: middleware blocked a configured tool, but the same tool supplied by a context provider executed with zero middleware callbacks. There needs to be an accessible registration point after context collection, or deferred wrapping of newly added tools. -
Copilot tool calls lose their call identity.
Thecopilot.go:493-502callsFuncTool.Calldirectly, without attaching the SDK’sToolCallID. The middleware executes, but receives an emptyCallIDdespite the SDK providing one. This path should attach the same internal invocation metadata astoolautocall.
This comment has been minimized.
This comment has been minimized.
Quim Muntal (qmuntal)
left a comment
There was a problem hiding this comment.
I’d avoid adding ProviderMiddlewares. Could we use Config.FunctionMiddlewares and apply it around each tool call instead? That would cover context-provider tools and AdditionalTools without requiring users to get the middleware ordering right.
.NET and Python also separate function middleware registration from execution.
|
Makes sense, I’ll switch to Config.FunctionMiddlewares so users don’t need to worry about ordering. |
|
Scope: public API, user-visible behavior Changed Go contract: Upstream evidence reviewed:
Result: aligned. Findings:
No unconditional-default or opt-in-gating mismatches were found:
|

Related to #949
Adds function-invocation middleware so applications can inspect the tool, call ID, arguments, result, and error for each call. Register callbacks in
agent.Config.FunctionMiddlewares, separately from agent middleware. Automatic tool execution and Copilot apply the callbacks to function tools regardless of whether they come from configured tools, run options, context providers, orAdditionalTools.FuncTool.Call(ctx, args)stays unchanged.Register the callback with
agent.Config{FunctionMiddlewares: []agent.FunctionInvocationMiddleware{audit}}. There is no ordering requirement relative to context providers or agent middleware. Callbacks can replace arguments, inspect or replace returned results/errors, or skip the underlying tool by not callingnext. Skipping one invocation does not stop the whole agent loop. Tool schemas and approval requirements are preserved, and callbacks run after approval. Call identity is exposed throughFunctionInvocationContext.CallID; bothtoolautocalland Copilot supply it through internal context helpers.#949 mentions normalized arguments; this implementation exposes raw JSON to match Go's existing
FuncTool.CallAPI. Validation and normalization remain the underlying tool's responsibility.Registration copies the callback slice and ignores nil callbacks. Execution uses internal tool wrappers with the first registered callback outermost.
AdditionalToolsremain absent from provider requests, and request tools retain precedence. Missing call IDs remain empty, and IDs are for correlation, not authorization or cross-run idempotency.Validation: full race-enabled suite (
go test -race ./...), targeted regression tests, and golangci-lint (zero issues). Tests are in existing files and cover concurrent calls, approval/session restoration, context propagation, callback ordering, argument/result changes, errors, and repeated runs. Provider tests cover allowing/blocking configured and context-provider tools throughNewChatCompletionsAgent, and Copilot call IDs with and without trace contexts. An offline sample was also run locally.