Repository navigation
fix(web_core): route unimplemented functions to the agent with callAgentFunction #3735
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
2b58c94
404acca
14d2e3d
6f0beb3
7bfc104
c5d1bb4
fe338c8
e9458f5
c5fae40
db15db0
10c6ac2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -1428,16 +1428,19 @@ This message is sent by the renderer to execute a function remotely on the agent | |||||
|
|
||||||
| When the renderer evaluates a `FunctionCall` (from an action handler, validation check, or dynamic value), it determines the execution target using implicit fallback routing: | ||||||
|
|
||||||
| 1. **Local Lookup:** The renderer checks if a local renderer-side function with that name is registered in its local catalog/registry. If found, the renderer executes the function locally. | ||||||
| 2. **Fallback to Agent RPC:** If the function is not registered in the local renderer catalog, the renderer assumes it is an agent-side function and dispatches a `callAgentFunction` message over the protocol. | ||||||
| 3. **Agent Error Handling:** If the agent does not recognize the function name (or if parameter validation fails on the server), the agent MUST return an `agentFunctionResponse` message containing an `error` payload (`code: "UNKNOWN_FUNCTION"` or `"INVALID_FUNCTION_CALL"`). The renderer then handles the error state locally (e.g., via component error boundaries or fallbacks). | ||||||
| 1. **Catalog Resolution:** The renderer resolves the catalog the call runs in, from its explicit `catalogId` or the surface default `catalogId` (see [Mixable catalogs and component resolution logic](#mixable-catalogs-and-component-resolution-logic)). A call whose catalog cannot be resolved is a catalog error, and is not routed to the agent. | ||||||
| 2. **Local Lookup:** The renderer checks whether it has a local implementation of the function in that catalog. A function counts as registered locally only if the renderer can execute it. If it can, the renderer executes the function locally. | ||||||
| 3. **Fallback to Agent RPC:** If the renderer has no local implementation, the renderer assumes it is an agent-side function and dispatches a `callAgentFunction` message over the protocol. This covers a function that the catalog does not declare, and a function that the catalog declares without the renderer implementing it (a schema-only declaration). For a declared function, the renderer validates the arguments against the declared schema before dispatching. | ||||||
| 4. **Agent Error Handling:** If the agent does not recognize the function name (or if parameter validation fails on the server), the agent MUST return an `agentFunctionResponse` message containing an `error` payload (`code: "UNKNOWN_FUNCTION"` or `"INVALID_FUNCTION_CALL"`). The renderer then handles the error state locally (e.g., via component error boundaries or fallbacks). | ||||||
|
|
||||||
| Fallback routing only applies to function calls that the renderer accepts. A renderer that rejects payloads naming functions absent from its catalogs must declare the agent-side functions it expects, without implementing them, for those calls to reach the agent. | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Line 1436 uses lowercase
Suggested change
🤖 Agent-generated |
||||||
|
|
||||||
| **Properties:** | ||||||
|
|
||||||
| - `callAgentFunction` (object, required): | ||||||
| - `surfaceId` (string, required): The surface ID where the call originated. | ||||||
| - `functionCallId` (string, required): A unique identifier for this invocation instance. The agent MUST copy this ID verbatim into the return `agentFunctionResponse`. | ||||||
| - `callFunction` (object, required): The description of the function call (`@call`, `catalogId`, `args`). | ||||||
| - `callFunction` (object, required): The description of the function call (`@call`, `catalogId`, `args`). The renderer SHOULD set `catalogId` to the catalog the call resolved to, even when the originating `FunctionCall` omits it and relies on the surface default, so that the agent does not need to track each surface's default catalog. `args` carries the resolved argument values. | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Line 1443 uses RFC 2119
Suggested change
🤖 Agent-generated |
||||||
|
|
||||||
| **Example:** | ||||||
|
|
||||||
|
|
@@ -1449,6 +1452,7 @@ When the renderer evaluates a `FunctionCall` (from an action handler, validation | |||||
| "functionCallId": "verify_provider_99", | ||||||
| "callFunction": { | ||||||
| "@call": "verifyProvider", | ||||||
| "catalogId": "https://example.com/catalogs/healthcare.json", | ||||||
| "args": { | ||||||
| "providerId": "PRV-102" | ||||||
| } | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,6 @@ | ||||||||
| ## Unreleased | ||||||||
|
|
||||||||
| - (v1_0) A function call whose function the renderer does not implement (undeclared in the catalog the call resolves to, or declared schema-only) is routed to the agent with `callAgentFunction`, as the specification's fallback routing defines, instead of failing with an `EXPRESSION_ERROR`. This applies to bindings, check rules, `action.functionCall`, and `action.event` `context`/`userMessage`. The value is pending (`undefined`) until the `agentFunctionResponse` arrives; an agent error or a timeout dispatches an `EXPRESSION_ERROR`. The outbound call always names the catalog the call resolved to, even when the payload relied on the surface default. Arguments of a schema-only function are validated against its schema before the call is sent. Reactive bindings share in-flight calls and cache settled answers per surface (bounded to 256 entries, keyed by canonically sorted argument keys, evicted on failure or surface deletion), while imperative action calls always execute fresh without sharing or caching. In a binding, check, or action, a function whose argument is still pending (such as an agent call) waits for it to settle instead of running with `undefined`, and stops without running or re-reporting if a nested argument fails. `MessageProcessor.dispose()` now disposes all surfaces in `model` in addition to `rpc`, and deleting a surface cancels its pending RPC calls. `MessageProcessor` enables fallback routing on v1.0 surfaces when it has an `outboundListener`; without one, such calls still fail locally. Adds `SurfaceModel.agentFunctionCaller`, `SurfaceModel.callAgentFunction`, `SurfaceModel.disposed`, `RpcHandler.cancelSurfaceCalls`, and the `AgentFunctionCall` and `AgentFunctionCaller` types. [#3415](https://github.com/a2ui-project/a2ui/issues/3415), [#3536](https://github.com/a2ui-project/a2ui/issues/3536) | ||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
🤖 Agent-generated |
||||||||
| - `DataContext.resolveSignal` seeds the result of a function call that returns a `Signal` with the signal's current value before it creates the forwarding `effect`. Under a reactivity backend whose effects run on the next flush rather than on creation (the Angular adapter), an enclosing call such as `not(areComponentsValid(...))` previously read `undefined` for that argument on its first evaluation and dispatched a spurious `EXPRESSION_ERROR`. | ||||||||
| - **BREAKING CHANGE**: A catalog's `invoker` refuses a function whose `allowedCallers` is `'agentOnly'` with an `A2uiExpressionError`, before running it, so a property binding, check or function call action that calls one reports an `EXPRESSION_ERROR` instead of running it, as the v1.0 specification requires. `RpcHandler` still runs such a function for an agent's `callRendererFunction`. Recommended migration: set `allowedCallers` to `'rendererOrAgent'` on a function both the renderer and the agent call ([#3483](https://github.com/a2ui-project/a2ui/issues/3483), [#3557](https://github.com/a2ui-project/a2ui/pull/3557)). | ||||||||
| - **BREAKING CHANGE**: Unify the v1.0 protocol version wire value on `'1.0'` (without a `'v'` prefix) across message envelopes, `MessageProcessor.getRendererCapabilities()`, `RpcHandler`, and catalog documents (`ProtocolVersion` uses `'1.0'`), while accepting `'v1.0'` in `VersionAdapterFactory.getAdapter` and `Catalog.fromJson`. [#3097](https://github.com/a2ui-project/a2ui/issues/3097), [#3726](https://github.com/a2ui-project/a2ui/pull/3726) | ||||||||
|
|
||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Steps 2 and 3 dispatch
callAgentFunctionfor any unimplemented function, omittingallowedCallers: "agentOnly", passiverequiresUserActivation: true, and@system functions. Implementers following Steps 1 to 3 verbatim will forwardagentOnlyfunctions, passiverequiresUserActivationcalls, or unknown@functions to the agent.🤖 Agent-generated