Repository navigation
Conversation
There was a problem hiding this comment.
Code Review
This pull request implements the agent-side capability to call renderer functions via callRendererFunction (v1.0). It introduces RendererFunctionCalls to manage pending calls and futures, A2uiRendererCallsStore to maintain these calls per session, and updates a2ui_rpc_callback and a2ui_rpc_interceptor to handle and complete RPC-only tasks. Conformance tests and schemas have also been updated. The review feedback highlights a potential issue in rpc_callback.py where a mixed batch of messages can cause pending responses to time out without being resolved, and suggests using the RpcErrorCode enum instead of a hardcoded string in renderer_function_calls.py for consistency.
4b79034 to
fb71c3a
Compare
fb71c3a to
1ac6f09
Compare
Audit the agent functions feature blueprint against the Python, TS and Dart branches and fix the drift: - The version check runs first for every message kind, so a callAgentFunction with a missing or other version raises even with a functionCallId; a schema-failing call with a readable id still gets an INVALID_FUNCTION_CALL reply in all three SDKs. - The timeout option is agent_function_timeout (seconds) in Python, agentFunctionTimeoutMs in TypeScript and Duration agentFunctionTimeout in Dart; the sketch now uses a neutral name. - The callback signatures per SDK, including Dart's FutureOr<void> callbacks that still decline with false. - Only Python's a2ui.rpc re-exports the message classes; a validation error raised by a handler is INVALID_FUNCTION_CALL in Dart only. Replace the follow-up note with section 5, the agent calling renderer functions as implemented by #3939, #3940 and #3941: RendererFunctionCalls, its checks and error codes, timeouts, resolve returning whether it owned the response, malformed responses, disposal, the optional catalog checks, the in-memory limitation on stateless servers, and the ADK integration. Requirements, links, conformance (renderer_function_calls.yaml, 23 cases) and implementation steps follow. The Python agent codebase blueprint says renderer calls come in the follow-up #3939. Signed-off-by: Nan Yu <nanyu@google.com>
1ac6f09 to
0f8ad2c
Compare
Audit the agent functions feature blueprint against the Python, TS and Dart branches and fix the drift: - The version check runs first for every message kind, so a callAgentFunction with a missing or other version raises even with a functionCallId; a schema-failing call with a readable id still gets an INVALID_FUNCTION_CALL reply in all three SDKs. - The timeout option is agent_function_timeout (seconds) in Python, agentFunctionTimeoutMs in TypeScript and Duration agentFunctionTimeout in Dart; the sketch now uses a neutral name. - The callback signatures per SDK, including Dart's FutureOr<void> callbacks that still decline with false. - Only Python's a2ui.rpc re-exports the message classes; a validation error raised by a handler is INVALID_FUNCTION_CALL in Dart only. Replace the follow-up note with section 5, the agent calling renderer functions as implemented by #3939, #3940 and #3941: RendererFunctionCalls, its checks and error codes, timeouts, resolve returning whether it owned the response, malformed responses, disposal, the optional catalog checks, the in-memory limitation on stateless servers, and the ADK integration. Requirements, links, conformance (renderer_function_calls.yaml, 23 cases) and implementation steps follow. The Python agent codebase blueprint says renderer calls come in the follow-up #3939. Signed-off-by: Nan Yu <nanyu@google.com>
Audit the agent functions feature blueprint against the Python, TS and Dart branches and fix the drift: - The version check runs first for every message kind, so a callAgentFunction with a missing or other version raises even with a functionCallId; a schema-failing call with a readable id still gets an INVALID_FUNCTION_CALL reply in all three SDKs. - The timeout option is agent_function_timeout (seconds) in Python, agentFunctionTimeoutMs in TypeScript and Duration agentFunctionTimeout in Dart; the sketch now uses a neutral name. - The callback signatures per SDK, including Dart's FutureOr<void> callbacks that still decline with false. - Only Python's a2ui.rpc re-exports the message classes; a validation error raised by a handler is INVALID_FUNCTION_CALL in Dart only. Replace the follow-up note with section 5, the agent calling renderer functions as implemented by #3939, #3940 and #3941: RendererFunctionCalls, its checks and error codes, timeouts, resolve returning whether it owned the response, malformed responses, disposal, the optional catalog checks, the in-memory limitation on stateless servers, and the ADK integration. Requirements, links, conformance (renderer_function_calls.yaml, 23 cases) and implementation steps follow. The Python agent codebase blueprint says renderer calls come in the follow-up #3939. Signed-off-by: Nan Yu <nanyu@google.com>
0f8ad2c to
ad3c4ab
Compare
Audit the agent functions feature blueprint against the Python, TS and Dart branches and fix the drift: - The version check runs first for every message kind, so a callAgentFunction with a missing or other version raises even with a functionCallId; a schema-failing call with a readable id still gets an INVALID_FUNCTION_CALL reply in all three SDKs. - The timeout option is agent_function_timeout (seconds) in Python, agentFunctionTimeoutMs in TypeScript and Duration agentFunctionTimeout in Dart; the sketch now uses a neutral name. - The callback signatures per SDK, including Dart's FutureOr<void> callbacks that still decline with false. - Only Python's a2ui.rpc re-exports the message classes; a validation error raised by a handler is INVALID_FUNCTION_CALL in Dart only. Replace the follow-up note with section 5, the agent calling renderer functions as implemented by #3939, #3940 and #3941: RendererFunctionCalls, its checks and error codes, timeouts, resolve returning whether it owned the response, malformed responses, disposal, the optional catalog checks, the in-memory limitation on stateless servers, and the ADK integration. Requirements, links, conformance (renderer_function_calls.yaml, 23 cases) and implementation steps follow. The Python agent codebase blueprint says renderer calls come in the follow-up #3939. Signed-off-by: Nan Yu <nanyu@google.com>
ad3c4ab to
52eeed5
Compare
Audit the agent functions feature blueprint against the Python, TS and Dart branches and fix the drift: - The version check runs first for every message kind, so a callAgentFunction with a missing or other version raises even with a functionCallId; a schema-failing call with a readable id still gets an INVALID_FUNCTION_CALL reply in all three SDKs. - The timeout option is agent_function_timeout (seconds) in Python, agentFunctionTimeoutMs in TypeScript and Duration agentFunctionTimeout in Dart; the sketch now uses a neutral name. - The callback signatures per SDK, including Dart's FutureOr<void> callbacks that still decline with false. - Only Python's a2ui.rpc re-exports the message classes; a validation error raised by a handler is INVALID_FUNCTION_CALL in Dart only. Replace the follow-up note with section 5, the agent calling renderer functions as implemented by #3939, #3940 and #3941: RendererFunctionCalls, its checks and error codes, timeouts, resolve returning whether it owned the response, malformed responses, disposal, the optional catalog checks, the in-memory limitation on stateless servers, and the ADK integration. Requirements, links, conformance (renderer_function_calls.yaml, 23 cases) and implementation steps follow. The Python agent codebase blueprint says renderer calls come in the follow-up #3939. Signed-off-by: Nan Yu <nanyu@google.com>
52eeed5 to
3615281
Compare
Audit the agent functions feature blueprint against the Python, TS and Dart branches and fix the drift: - The version check runs first for every message kind, so a callAgentFunction with a missing or other version raises even with a functionCallId; a schema-failing call with a readable id still gets an INVALID_FUNCTION_CALL reply in all three SDKs. - The timeout option is agent_function_timeout (seconds) in Python, agentFunctionTimeoutMs in TypeScript and Duration agentFunctionTimeout in Dart; the sketch now uses a neutral name. - The callback signatures per SDK, including Dart's FutureOr<void> callbacks that still decline with false. - Only Python's a2ui.rpc re-exports the message classes; a validation error raised by a handler is INVALID_FUNCTION_CALL in Dart only. Replace the follow-up note with section 5, the agent calling renderer functions as implemented by #3939, #3940 and #3941: RendererFunctionCalls, its checks and error codes, timeouts, resolve returning whether it owned the response, malformed responses, disposal, the optional catalog checks, the in-memory limitation on stateless servers, and the ADK integration. Requirements, links, conformance (renderer_function_calls.yaml, 23 cases) and implementation steps follow. The Python agent codebase blueprint says renderer calls come in the follow-up #3939. Signed-off-by: Nan Yu <nanyu@google.com>
3615281 to
bfa38e4
Compare
Audit the agent functions feature blueprint against the Python, TS and Dart branches and fix the drift: - The version check runs first for every message kind, so a callAgentFunction with a missing or other version raises even with a functionCallId; a schema-failing call with a readable id still gets an INVALID_FUNCTION_CALL reply in all three SDKs. - The timeout option is agent_function_timeout (seconds) in Python, agentFunctionTimeoutMs in TypeScript and Duration agentFunctionTimeout in Dart; the sketch now uses a neutral name. - The callback signatures per SDK, including Dart's FutureOr<void> callbacks that still decline with false. - Only Python's a2ui.rpc re-exports the message classes; a validation error raised by a handler is INVALID_FUNCTION_CALL in Dart only. Replace the follow-up note with section 5, the agent calling renderer functions as implemented by #3939, #3940 and #3941: RendererFunctionCalls, its checks and error codes, timeouts, resolve returning whether it owned the response, malformed responses, disposal, the optional catalog checks, the in-memory limitation on stateless servers, and the ADK integration. Requirements, links, conformance (renderer_function_calls.yaml, 23 cases) and implementation steps follow. The Python agent codebase blueprint says renderer calls come in the follow-up #3939. Signed-off-by: Nan Yu <nanyu@google.com>
bfa38e4 to
8aa700d
Compare
gspencergoog
left a comment
There was a problem hiding this comment.
This pull request (#3939) implements v1.0 agent-to-renderer function calls (RendererFunctionCalls, RpcErrorCode.TOO_MANY_PENDING, A2uiRendererCallsStore, a2ui_rpc_interceptor, a2ui_rpc_callback renderer_calls support, and the 30-case conformance/agent/renderer_function_calls.yaml suite). Both threads from the prior review round are resolved cleanly in commit 3933a79cf. No merge blockers were found, though two medium-severity state edge cases (M1 in off-thread dispose() handling and M2 in _RpcTaskTracker stale task_id retention), one medium-severity conformance documentation mismatch (M3), and two low-severity assertion and logging items (L1, L2) remain.
🤖 Agent-generated
Restore the agent's calls to renderer functions, split out of #3793-#3795 during review, on top of RendererToAgentMessageProcessor: - a2ui.rpc.RendererFunctionCalls (with DEFAULT_RENDERER_CALL_TIMEOUT and RendererFunctionResponseInput) builds callRendererFunction messages and settles their futures. Its resolve method is the processor's on_renderer_function_response callback. - Review fixes from #3793: an empty function name is rejected with INVALID_FUNCTION_CALL; an empty surface_id counts as omitted; a malformed rendererFunctionResponse whose functionCallId names a pending call fails that call at once (UNKNOWN_ERROR) instead of leaving it to time out; the timer test asserts timer.cancelled(). - New is_pending(function_call_id), so a framework settles only responses to its own calls and passes others (such as responses to calls a model wrote through an inference format) to the model's turn, as the review asked. - a2ui-adk: a2ui_rpc_callback takes renderer_calls (catalogs becomes optional), settles responses to pending calls through the processor, and fails a pending call on a malformed response. A2uiRendererCallsStore and a2ui_rpc_interceptor come back. callRendererFunction and rendererFunctionResponse stay in the model's prompt, since unmatched responses now reach the model. - Conformance: conformance/agent/renderer_function_calls.yaml (the 14 call_renderer_function cases plus empty-name and empty-surfaceId cases), CallRendererFunctionTest / RendererCallState / RendererCallStep in the schema, README and harness-guide text, and a Python runner on a fake clock that delivers responses through the processor. - README, CHANGELOG and agent_development.md sections. Open design question from the review, left as is: whether max_pending and the non-spec TOO_MANY_PENDING code are needed, since the agent's own code starts these calls. Pending calls are in-memory, so this suits a server that keeps a conversation on one process; stateless multi-replica servers handle rendererFunctionResponse in their own callback. Signed-off-by: Nan Yu <nanyu@google.com>
web_core's harness reads every conformance suite, and call_renderer_function is agent-side only, like the other agent actions it already skips. Signed-off-by: Nan Yu <nanyu@google.com>
… the agent A request whose rendererFunctionResponse messages answered both pending calls and calls the session didn't make (or that also held text, or a callAgentFunction with no catalogs) went to the agent without settling the pending calls, so they waited for their timeout. The callback now settles those calls first and then lets the agent run. Also add RpcErrorCode.TOO_MANY_PENDING to a2ui_core, matching TS and Dart, and raise it by enum in RendererFunctionCalls. Signed-off-by: Nan Yu <nanyu@google.com>
… responses - RendererFunctionCalls.resolve returns True when the response named a pending call (settled, or failed if malformed) and False for an unknown, late or duplicate one, so as on_renderer_function_response it declines responses it doesn't own and the processor reports them as not handled. - a2ui_rpc_callback processes each message with process_message. Responses (and other non-call messages) go first through a processor whose on_renderer_function_response is calls.resolve, so pending calls are always settled, even in requests that go to the agent; the request is answered only when every part is A2UI and every message is handled, and only then are agent function calls run. Otherwise it returns None. - The renderer-call conformance runner checks that a response is handled exactly when its call is pending; README, dev guide and CHANGELOGs follow. Signed-off-by: Nan Yu <nanyu@google.com>
call_renderer_function cases take an optional args.catalogs, a list of
RendererCallCatalog entries ({catalogId, functions: {name: {allowedCallers?,
requiresUserActivation?}}}) that the harness builds in code as v1.0
catalogs, and a case may expect building the helper to fail
(expect_error) instead of having steps. Seven new cases in
renderer_function_calls.yaml cover rendererOnly and omitted allowedCallers,
requiresUserActivation, a function or catalog the agent's catalogs lack, an
allowed rendererOrAgent call, and two catalogs with one id. README and the
harness guide describe the shape; the Python runner builds the catalogs
and passes them to RendererFunctionCalls (implemented in the next commit).
Signed-off-by: Nan Yu <nanyu@google.com>
… catalogs RendererFunctionCalls takes optional catalogs. Two catalogs with one id raise A2uiCatalogError at construction. With catalogs, call() checks the target after the empty-name, empty-catalogId and duplicate-id checks and before anything is built, registered or timed: a catalogId that names no catalog, or a function the catalog lacks, raises UNKNOWN_FUNCTION; a rendererOnly function (allowedCallers set to it or left unset) raises INVALID_FUNCTION_CALL saying the agent may not call it; a function that requires user activation raises INVALID_FUNCTION_CALL, since an agent call has no user gesture. Arguments aren't validated. Without catalogs nothing changes. Unit tests, agent_development.md, README and CHANGELOG follow. A2uiRendererCallsStore keeps building RendererFunctionCalls without catalogs: the active catalogs are per session, so a store-wide setting isn't the right shape. Signed-off-by: Nan Yu <nanyu@google.com>
RendererFunctionCalls looks the target up in catalog.renderer_functions, so an agent function, however it was built (including with from_api from a rendererOrAgent declaration), is rejected with UNKNOWN_FUNCTION: "Catalog 'c' has no renderer function 'f'." Tests, docs and the CHANGELOG line follow. Signed-off-by: Nan Yu <nanyu@google.com>
…all checks Seven new call_renderer_function cases: an empty given functionCallId, a name that isn't an identifier, a name starting with @, args that aren't an object, an argument key that isn't an identifier, the name checked before the catalogId, and the args checked before the catalogs. The call step takes functionCallId, and its args may be any JSON value. The Python runner passes functionCallId; the checks themselves come in the next commit. Signed-off-by: Nan Yu <nanyu@google.com>
… shared order RendererFunctionCalls.call follows the cross-SDK order: the timeout, disposal, an empty given functionCallId (now INVALID_FUNCTION_CALL instead of a generated id), a duplicate id, max_pending, an empty name, an empty catalogId, a name that isn't a UAX #31 identifier or starts with @, args that aren't a mapping with identifier keys and JSON values (NaN and infinity refused, nested @call/@path objects left alone), and last the catalogs. timeout and default_timeout must be finite positive numbers (no bools); only default_timeout=None turns the limit off, and a per-call None uses the default. A2uiRendererCallsStore checks its settings the same way. Unit tests cover every rule and the order, including NaN and infinity in args and timeouts. The conformance README and harness guide say how a statically typed SDK meets the non-object args case. agent_development.md, README and CHANGELOG follow. Signed-off-by: Nan Yu <nanyu@google.com>
… calls Signed-off-by: Nan Yu <nanyu@google.com>
…alls
- RendererFunctionCalls ignores responses and timeouts once disposed, even
before an off-thread dispose has failed the calls on the loop, so the
calls fail with CANCELLED. A malformed response is logged by error type
at warning level and in full at debug level.
- a2ui_rpc_interceptor forgets a task's earlier RPC-only request when a new
request on the task arrives, and moves a task it sees again to the end of
the eviction order.
- a2ui_rpc_callback warns about passing an invalid message on to the agent
only when RendererFunctionCalls.resolve doesn't take it, and logs the
error by type.
- The renderer function calls suite gets closed expect_error shapes,
RendererCallError ({code, message?}) on steps and RendererCallsBuildError
({category: CatalogError, message?}) on cases, and the Python runner
rejects other keys. The docs say only the Python agent harness runs the
suite so far.
Signed-off-by: Nan Yu <nanyu@google.com>
Audit the agent functions feature blueprint against the Python, TS and Dart branches and fix the drift: - The version check runs first for every message kind, so a callAgentFunction with a missing or other version raises even with a functionCallId; a schema-failing call with a readable id still gets an INVALID_FUNCTION_CALL reply in all three SDKs. - The timeout option is agent_function_timeout (seconds) in Python, agentFunctionTimeoutMs in TypeScript and Duration agentFunctionTimeout in Dart; the sketch now uses a neutral name. - The callback signatures per SDK, including Dart's FutureOr<void> callbacks that still decline with false. - Only Python's a2ui.rpc re-exports the message classes; a validation error raised by a handler is INVALID_FUNCTION_CALL in Dart only. Replace the follow-up note with section 5, the agent calling renderer functions as implemented by #3939, #3940 and #3941: RendererFunctionCalls, its checks and error codes, timeouts, resolve returning whether it owned the response, malformed responses, disposal, the optional catalog checks, the in-memory limitation on stateless servers, and the ADK integration. Requirements, links, conformance (renderer_function_calls.yaml, 23 cases) and implementation steps follow. The Python agent codebase blueprint says renderer calls come in the follow-up #3939. Signed-off-by: Nan Yu <nanyu@google.com>
8aa700d to
cb9c59f
Compare
gspencergoog
left a comment
There was a problem hiding this comment.
This pull request implements the agent-initiated side of v1.0 RPC (callRendererFunction and rendererFunctionResponse) across the Python SDK stack (a2ui-core, a2ui-agent-sdk, a2ui-a2a, and a2ui-adk) and adds the shared conformance/agent/renderer_function_calls.yaml suite with extensions in a2a_integration.yaml and adk_extensions.yaml. All seven prior review threads from earlier rounds (including the off-thread dispose() race in RendererFunctionCalls, stale task retention and FIFO eviction ordering in _RpcTaskTracker, conformance documentation for TypeScript and Dart harnesses, schema-backed error key checking in test_renderer_function_calls_conformance.py, and warning suppression for claimed malformed responses in rpc_callback.py) have been verified resolved at cb9c59f96a8ed741f3063da86b6d1255b046bd0c.
🤖 Agent-generated
Stacked on #3795. Review the top ten commits:
feat(python-agent): call renderer functions from the agent (#3535), followed by the review-fix and conformance commits covering pending call settlement,RendererFunctionCalls.resolvedeclining unknown responses, catalog target checks, the shared validation order, and non-emptyerror.messageassertions. Draft until #3792-#3795 land and the open questions below are settled.Part of #3535.
Description
#3793 first shipped the agent's calls to renderer functions (
callRendererFunction) together with agent functions. The review on #3793 questioned whether in-memory pending calls fit stateless HTTP servers, where therendererFunctionResponsecan arrive in a later request on another replica, and how responses to calls a model wrote itself reach the model. To keep agent functions moving, renderer function calls were split out of #3793-#3795 into this PR, on top ofRendererToAgentMessageProcessor. The code is the code reviewed on #3793, renamed to fit the processor, with the review fixes below. TypeScript (#3940) and Dart (#3941) follow the same split.RendererFunctionCallsina2ui.rpc, one per conversation.call(catalog_id=..., name=..., args=..., surface_id=None, timeout=None)returns(CallRendererFunctionMessage, asyncio.Future)without waiting. It generates thefunctionCallId, always setscatalogId, and builds the message through the schema model.resolve(message)settles the matching future with the value, or withA2uiRpcErrorcarrying the renderer's code. It follows the renderer-sideRpcHandler:resolvemay be called from any thread and goes throughloop.call_soon_threadsafe.loop.call_later(30 s by default,DEFAULT_RENDERER_CALL_TIMEOUT, as inRpcHandler). Settling or cancelling a call removes its entry and timer.resolvereturnsTruewhen the response named a pending call, which it settles (or fails, if the response is malformed), andFalsefor an unknown, late or duplicate response, which it logs at debug level and ignores. Nothing from the network raises.dispose(reason)fails pending calls (CANCELLED) and makes latercall()s raise (DISPOSED).async withdisposes on exit.max_pendingmakescall()raise withRpcErrorCode.TOO_MANY_PENDING, which this PR adds toa2ui_core, as TS and Dart have it.is_pending(function_call_id)tells whether a response answers one of the instance's calls.How it plugs into the processor:
resolveis theon_renderer_function_responsecallback ofRendererToAgentMessageProcessor. The processor validates the response and hands it over, and becauseresolvereturnsFalsefor a response it doesn't own, the processor reports that response as not handled. The inference formats also compile a call the model writes on its own (such as<a2ui>getLocation()</a2ui>) into acallRendererFunctionthatRendererFunctionCallsnever saw, so responses to those calls reach the model's turn. The processor's API from feat(python-agent): answer agent functions through RendererToAgentMessageProcessor #3793 doesn't change:ADK (
rpc_callback.py).a2ui_rpc_callbacktakesrenderer_calls, a sync or async provider of the session'sRendererFunctionCalls, andcatalogsbecomes optional (pass one or both). It processes each A2UI message of the user content withprocess_message, usingcalls.resolveason_renderer_function_response:callAgentFunctionwith no catalogs. A malformed response that names a pending call fails that call.callAgentFunctionmessages run, and their replies returned. Otherwise the callback returnsNoneand the agent runs with the whole request.A2uiRendererCallsStorekeeps oneRendererFunctionCallsper session in memory, next toA2uiProcessorStore.get(ctx)creates the session's instance on first use (and replaces one disposed elsewhere), so tools and the callback share it; it is therenderer_callsprovider.remove(session_id)disposes it, failing its pending calls, anddispose()disposes all.a2ui_rpc_interceptor()returns anExecuteInterceptorforA2aAgentExecutorConfig.execute_interceptors. ADK's executor marks a taskcompletedonly when the final status message has parts, so a request holding onlyrendererFunctionResponsemessages, which has nothing to send back, would endworking. The interceptor notes the task when every part of the request is acallAgentFunctionorrendererFunctionResponse, and changes that task's final status fromworkingwith no message parts tocompleted. Other final states and other tasks are untouched. The noted IDs are capped at 1024, since a request that fails between the hooks never reachesafter_agent.Prompt.
callRendererFunctionandrendererFunctionResponseare no longer removed from the model's prompt (the first version of feat(python-a2a,adk): answer agent function calls in A2A and ADK #3795 removed them; feat(python-a2a,adk): answer agent function calls in A2A and ADK #3795 now removes onlycallAgentFunctionandagentFunctionResponse), since a response that answers no pending call now goes to the model.Conformance. New suite
conformance/agent/renderer_function_calls.yaml(30 cases). Steps (call,respond,advance_ms,dispose) run on a fake clock that onlyadvance_msmoves, so no case waits on real time. The cases cover the message a call builds; a response settling the matching call with a value (includingnull) or the renderer's error code; unknown, duplicate and late responses being ignored; the 30 s default timeout, per-call and per-instance timeouts, and no timeout after a call settles;dispose;max_pending; catalog checks; and validation order.conformance_schema.jsongetsCallRendererFunctionTest,RendererCallStateandRendererCallStep, and the README and harness guide describe the suite. The Python runner delivers responses throughRendererToAgentMessageProcessorand checks that a response is handled when and only when its call is pending. web_core's conformance runner skipscall_renderer_functionas agent-side only.Docs. A "Calling Renderer Functions" section in
agent_development.md, and the READMEs and CHANGELOGs ofa2ui_agent,a2ui_adkanda2ui_core.Review fixes from the first review of #3793, applied here:
A2uiRpcError(INVALID_FUNCTION_CALL), like an emptycatalog_id.surface_idcounts as omitted: the message leavessurfaceIdout.rendererFunctionResponsewhosefunctionCallIdnames a pending call fails that call at once (UNKNOWN_ERROR), instead of leaving it to time out. A malformed response for no pending call is still logged and ignored.timer.cancelled().After the second review of #3793, callbacks may decline a message by returning
False, soresolvenow returns whether it owned the response, and the ADK callback relies on that instead of checkingis_pendingfor every response itself.Open design questions, kept for discussion:
max_pendingand theTOO_MANY_PENDINGcode, which the protocol doesn't define. The agent's own code starts these calls, so a limit may not be needed; it is there to stop a looping model from piling up calls.rendererFunctionResponsein its ownon_renderer_function_responsecallback instead, for example by looking up saved state byfunctionCallId. The docs say so.Catalog checks (after review on #3793, #3793 (comment)):
RendererFunctionCallstakes an optionalcatalogsargument. With it,call()checks the target before it builds a message, registers a pending call or starts a timer. An unknowncatalogId, or a name that isn't one of that catalog's renderer functions (agent functions included), getsUNKNOWN_FUNCTION. ArendererOnlyfunction (or one that leaves outallowedCallers) getsINVALID_FUNCTION_CALL, and so does one withrequiresUserActivation: true, since a call from the agent has no user gesture. Two catalogs with the same ID are a catalog error. Withoutcatalogs, nothing changes.A2uiRendererCallsStoredoesn't pass catalogs yet, since the active catalogs differ per session.call()runs its checks in the same order in all three SDKs:functionCallIdmax_pendingcatalogIdargschecks: a JSON object, identifier keys, and JSON values (no NaN or infinity)Only the instance default timeout can be "no limit".
Changes after the latest review: Third review: after an off-thread
dispose(),is_pending,_fail_malformed,_settleand_time_outreturn early, so the call ends CANCELLED;a2ui_rpc_interceptor.before_agentclears a stale task id and refreshes eviction order; the conformance docs say only the Python harness runsrenderer_function_calls.yamlon this branch; the schema gets closedRendererCallError({code, message?}) andRendererCallsBuildError({category: CatalogError, message?}) shapes that the runner enforces;_process_otherdoesn't warn about a malformed response thatresolvetook.Tests
test_renderer_function_calls.py: message shape, empty name andsurface_id, matching, error responses, unknown/late/duplicate/malformed responses (including a malformed response to a pending call) and whatresolvereturns for each,is_pending,resolveas the processor callback (handled or declined),resolveanddisposefrom another thread, timeouts with the timer cancelled on settle, cancellation,async with,max_pending, and loop binding.test_renderer_function_calls_conformance.pyruns all 30 cases of the new suite.a2ui_adk:test_rpc_callback.pycovers settling a pending call, calls and responses together, a response to an unknown call going to the agent, a pending call settled in a request that goes to the agent (next to a response to an unknown call, next to text, or next to a call with no catalogs), a repeated response being ignored, a malformed response failing the pending call, the settle throughA2aAgentExecutor, and the interceptor (an RPC-only task endscompleted; a task with a result, other requests and a failed RPC task are unchanged).test_renderer_calls_store.pycovers the store.uv run pytest python/a2ui_agent/tests/rpc python/a2ui_agent/tests/conformance: 251 passed.