Repository navigation
swift (v1.0): unregistered @call function is not routed to agent via callAgentFunction in bindings, checks, and action.functionCall #3689
Description
Summary
In protocol v1.0, a FunctionCall ({"@call": "..."}) whose function has no local renderer implementation in the catalog should be routed to the agent via callAgentFunction (specification: specification/v1_0/docs/a2ui_protocol.md → § "Function Location Resolution & Fallback Routing", lines 1395–1426).
While A2UICore implements the programmatic RPC API (MessageProcessor.callAgentFunction and RPCHandler.callAgentFunction), DataContext and NodeResolver have no integration with RPCHandler:
- When a property binding or validation check evaluates a function not implemented locally,
DataContext.evaluateFunctionCallfails immediately withFunctionError.functionNotFound(callName)and resolves to.null(swift/core/Sources/A2UICore/State/DataContext.swift:152-158). - When a
Button's action is configured asfunctionCall({"action": {"functionCall": {"@call": "..."}}}),NodeResolverinvokesevaluateDynamicValue(funcCallVal)(swift/core/Sources/A2UICore/Resolution/NodeResolver.swift:1490), triggering the sameFunctionError.functionNotFoundpath inDataContext. - No
CallAgentFunctionMessageis dispatched over the wire, and the agent never receives the RPC invocation.
In contrast, the Dart SDK (dart/a2ui_core) injects callAgentFunction into SurfaceModel and DataContext, correctly routing unregistered calls to the agent and awaiting AgentFunctionResponseMessage (tested in dart/a2ui_core/test/agent_function_fallback_test.dart).
Specification Reference
From specification/v1_0/docs/a2ui_protocol.md (§ "Function Location Resolution & Fallback Routing"):
When the renderer evaluates a
FunctionCall(from an action handler, validation check, or dynamic value), it determines the execution target using implicit fallback routing:
- 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.
- 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
callAgentFunctionmessage over the protocol.- Agent Error Handling: If the agent does not recognize the function name, the agent MUST return an
agentFunctionResponsemessage containing anerrorpayload. The renderer then handles the error state locally.
Affected Files
swift/core/Sources/A2UICore/State/DataContext.swift(lines 130–168)swift/core/Sources/A2UICore/Resolution/NodeResolver.swift(lines 1456–1495)swift/core/Sources/A2UICore/Processing/MessageProcessor.swift(wiring to surface / resolution context)swift/core/Sources/A2UICore/Models/SurfaceViewModel.swift
Steps to Reproduce
- In a v1.0 surface with the Basic Catalog, define a
Buttonwith a server-side action:{ "id": "btn", "component": "Button", "child": "btn_text", "action": { "functionCall": { "@call": "createIssue", "args": {"title": "Sample Issue"} } } } - Set up
MessageProcessorwith an outbound listener to observe emitted renderer-to-agent messages. - Trigger the button's action.
- Expected: A
CallAgentFunctionMessageforcreateIssueis emitted to the outbound listener. - Actual:
DataContextlogsFunctionError.functionNotFound("createIssue"), returns.null, and no outbound message is emitted.
Suggested Fix
- Follow the Dart pattern (
dart/a2ui_core/lib/src/processing/processor.dart:665anddata_context.dart:724): pass acallAgentFunctiondelegate fromMessageProcessorintoSurfaceViewModel/DataContext. - In
DataContext.evaluateFunctionCall, if the function is not found locally on a v1.0 surface and an RPC caller is available:- Dispatch
callAgentFunctionvia the surface caller. - Return a placeholder/pending state that completes with the
agentFunctionResponsevalue.
- Dispatch
- For action closures (
ResolvedActioninNodeResolver), await the async RPC result when activated. - On
erroror timeout, invokefunctionHandler?.handleFunctionError(...)with the structured RPC error.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTodo
Activity
nan-yu commentedon Oct 30, 2026
@gspencergoog, heads-up: #3741 fixes this issue, so I've added
Fixes #3689to its description.It routes v1.0 function calls the Swift core can't run to the agent with
callAgentFunction, from bindings, check rules, andaction.functionCall:MessageProcessorsetsSurfaceViewModel.agentFunctionCalleron v1.0 surfaces whileoutboundListeneris set, and updates it if the listener changes later..nulluntilagentFunctionResponsearrives. Agent errors and timeouts are reported throughhandleFunctionErrorasEXPRESSION_ERROR.Buttonaction like the one in the repro sendscallAgentFunctionwhen triggered (sendsActionFunctionCallinAgentFunctionRoutingTests). Each trigger sends a fresh call.catalogId. Schema-only functions are routed too, with their arguments validated first.One difference from the suggested fix:
ResolvedActionstays a synchronous closure that starts the call, rather than one the caller awaits. Errors are still reported, and nested agent arguments settle before the call is sent.#3741 is stacked on #3735 → #3739 → #3740, and targets the Python branch for now. The
Fixeskeyword takes effect once it's retargeted tomainand merged.🤖 Agent-generated