Repository navigation
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces the "RendererFunctionCalls" class to manage agent-to-renderer function calls, handling pending calls, timeouts, cancellations, and response correlation, alongside adding the "TOO_MANY_PENDING" error code to "RpcErrorCode". The review feedback suggests refactoring the unit tests to use an "afterEach" hook for resetting fake timers instead of wrapping assertions in redundant "try...finally" blocks, which reduces boilerplate and prevents test pollution.
9ebf225 to
175ad53
Compare
175ad53 to
4cd64f5
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>
4cd64f5 to
ad272c1
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>
ad272c1 to
a75ec61
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>
a75ec61 to
ee83c0d
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>
ee83c0d to
d0f7198
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>
d0f7198 to
716de18
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>
716de18 to
4715e54
Compare
gspencergoog
left a comment
There was a problem hiding this comment.
This change adds RendererFunctionCalls to @a2ui/agent (and RpcErrorCode.TOO_MANY_PENDING to @a2ui/web_core) for v1.0 agent-to-renderer function calls (callRendererFunction -> rendererFunctionResponse), complete with pre-flight catalog permission and JSON argument validation, timeouts, cancellation, disposal, and 78 unit tests plus the 30-case conformance runner. Before merging, remove the unused seconds-based DEFAULT_RENDERER_CALL_TIMEOUT = 30.0 constant (which creates a unit-mismatch hazard alongside millisecond-based defaultTimeoutMs and timeoutMs options) and consolidate the ## Unreleased changelog entries with [#3940] pull request links.
🤖 Agent-generated
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>
Remove DEFAULT_RENDERER_CALL_TIMEOUT (30.0, in seconds), which passed to defaultTimeoutMs or timeoutMs would set a 30 ms timeout; keep DEFAULT_RENDERER_CALL_TIMEOUT_MS. Consolidate the RendererFunctionCalls changelog entries into one net entry and link #3940 from it and from the TOO_MANY_PENDING entry. Signed-off-by: Nan Yu <nanyu@google.com>
4715e54 to
0bc471e
Compare
Signed-off-by: Nan Yu <nanyu@google.com>
…calls conformance test Signed-off-by: Nan Yu <nanyu@google.com>
The renderer function calls tests wrapped each fake-timer test in try/finally to restore real timers. An afterEach hook in each describe does that once, even when an assertion fails. Signed-off-by: Nan Yu <nanyu@google.com>
… the response resolve returns true when it settled or failed a pending call and false when the functionCallId isn't pending (unknown, late or a duplicate). Used as onRendererFunctionResponse, it so declines the responses it doesn't own, such as those to calls the model wrote, and they reach the model. Signed-off-by: Nan Yu <nanyu@google.com>
RendererFunctionCalls takes an optional catalogs option. With it, call rejects a call before anything is registered or sent: UNKNOWN_FUNCTION for a catalog or function the catalogs don't have, INVALID_FUNCTION_CALL for a function the agent may not call (rendererOnly, allowedCallers omitted, or requiring user activation). Two catalogs with the same id are an A2uiCatalogError. Signed-off-by: Nan Yu <nanyu@google.com>
…cases The call_renderer_function runner builds each args.catalogs entry as a v1.0 catalog in code and passes it to RendererFunctionCalls, and a case with a top-level expect_error checks that building the helper fails. Signed-off-by: Nan Yu <nanyu@google.com>
…ctions An agent function in the agent's catalog (for example one built with AgentFunction.fromApi from a rendererOrAgent declaration) runs on the agent, so a call to it is rejected with UNKNOWN_FUNCTION. Signed-off-by: Nan Yu <nanyu@google.com>
… SDKs use RendererFunctionCalls.call checks the name before the catalogId, rejects a name starting with @, and checks the args (now including that every value is a JSON value, recursively, so NaN and Infinity are rejected) before the catalogs. timeoutMs is a plain optional number: a runtime null counts as omitted, and only defaultTimeoutMs: null disables timeouts. Signed-off-by: Nan Yu <nanyu@google.com>
The call step's args may be any JSON value, such as a list, for the cases that check args that aren't an object. Signed-off-by: Nan Yu <nanyu@google.com>
Signed-off-by: Nan Yu <nanyu@google.com>
…rocessor Signed-off-by: Nan Yu <nanyu@google.com>
Remove DEFAULT_RENDERER_CALL_TIMEOUT (30.0, in seconds), which passed to defaultTimeoutMs or timeoutMs would set a 30 ms timeout; keep DEFAULT_RENDERER_CALL_TIMEOUT_MS. Consolidate the RendererFunctionCalls changelog entries into one net entry and link #3940 from it and from the TOO_MANY_PENDING entry. Signed-off-by: Nan Yu <nanyu@google.com>
RendererFunctionCalls.resolve settled a call with its value when the
response also carried error: null. Like the processor's schema and the
Python SDK, a response now needs exactly one of value and error, so
error: null fails the call with UNKNOWN_ERROR; value: null still
settles it with null. Test that a response after dispose settles
nothing.
The renderer call conformance runner checks each expect_error against
its shape: a call step's {code, message?} and a case's
{category: CatalogError, message?}, with no other keys, and matches
message as a literal substring.
Signed-off-by: Nan Yu <nanyu@google.com>
0bc471e to
2e0f52d
Compare
gspencergoog
left a comment
There was a problem hiding this comment.
This pull request adds RendererFunctionCalls to @a2ui/agent and RpcErrorCode.TOO_MANY_PENDING to @a2ui/web_core to support v1.0 agent-to-renderer function calls (callRendererFunction and rendererFunctionResponse), along with unit and conformance tests, README usage documentation, and changelog entries. All Round 1 review threads have been resolved (DEFAULT_RENDERER_CALL_TIMEOUT removed, changelog entries consolidated with #3940 links, and response validation tightened), and all CI checks pass.
🤖 Agent-generated
Stacked on #3862. Review the top eleven commits:
feat(typescript): call renderer functions from the agent (#3535), followed by the review-fix and conformance commits coveringRendererFunctionCalls.resolvedeclining unknown responses, catalog target checks, the shared validation order,isPending, and routing conformance responses throughRendererToAgentMessageProcessor. Draft until #3862 lands and the open questions below are settled.Part of #3535.
Description
#3862 first shipped the agent's calls to renderer functions (
callRendererFunction) together with agent functions, as the Python stack did. The review on #3793 questioned whether in-memory pending calls fit stateless HTTP servers, where therendererFunctionResponsecan arrive in a later request on another replica. To keep agent functions moving, renderer function calls were split out of #3862 into this PR in every SDK (Python #3939, Dart #3941). The code is the code that was on #3862, renamed to fitRendererToAgentMessageProcessor, with the review fixes below.RendererFunctionCallsin@a2ui/agent, one per conversation.call({catalogId, name, args?, surfaceId?, timeoutMs?, functionCallId?})checks the call and returns thecallRendererFunctionmessage to send and a cancellable promise of the result, as a tuple or by name.resolvesettles the matching promise with the renderer's value, or rejects it withA2uiRpcErrorcarrying the renderer's code. It returnstruewhen it settled or failed a pending call, andfalsewhen thefunctionCallIdisn't pending (unknown, late or a duplicate).A call times out after
DEFAULT_RENDERER_CALL_TIMEOUT_MS(30 seconds, as on the renderer side) unlessdefaultTimeoutMs(whichnulldisables) ortimeoutMssays otherwise. Cancelling a call rejects it withCANCELLEDand frees its slot. Unknown, duplicate and late responses are ignored, andresolvereturnsfalsefor them.maxPendinglimits the calls in flight (TOO_MANY_PENDING), anddisposerejects the pending calls withCANCELLEDand refuses later calls withDISPOSED.@a2ui/web_coregetsRpcErrorCode.TOO_MANY_PENDING.How it plugs into the processor:
resolveis a bound function, passed toRendererToAgentMessageProcessorasonRendererFunctionResponse, so the processor validates the response and hands it over. Since callbacks may returnfalseto decline (from the second review of feat(python-agent): answer agent functions through RendererToAgentMessageProcessor #3793), a responseresolvedoesn't own, such as one to a call the model wrote, comes backhandled: falseand can go on to the model. The processor's API from feat(typescript): agent functions and RendererToAgentMessageProcessor #3862 doesn't change:The README gets a "Calling renderer functions" section, and both CHANGELOGs list the API.
Review fixes from #3793, applied here as in Python:
A2uiRpcError(INVALID_FUNCTION_CALL), like an emptycatalogId.surfaceIdcounts as omitted: the message leaves it out.rendererFunctionResponsethat carries a pending call'sfunctionCallIdfails that call right away withUNKNOWN_ERROR, instead of leaving it to time out.vi.getTimerCount()drops to 0), the counterpart of Python'stimer.cancelled()assertion.Open design questions, kept for discussion on #3939:
maxPendingand theTOO_MANY_PENDINGcode, which the protocol doesn't define. The agent's own code starts these calls, so a limit may not be needed.rendererFunctionResponsein its ownonRendererFunctionResponsecallback instead. The README says 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. The conformance runner supports the newcatalogscases. Checked locally againstrenderer_function_calls.yamlfrom #3939: 31/31 (30 cases and the non-empty check).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".
isPending(functionCallId)inspects whether a call is awaiting a response without settling it, matching Python and Dart.Changes after the latest review: Review: removed the seconds-based
DEFAULT_RENDERER_CALL_TIMEOUT; one consolidated CHANGELOG entry with the #3940 link, and the link onTOO_MANY_PENDING. Aligned with #3939 and #3941:resolvefails a pending call withUNKNOWN_ERRORforerror: null, neithervaluenorerror, extra keys or another version; a late response afterdispose()settles nothing; the conformance runner enforces the closedRendererCallErrorandRendererCallsBuildErrorshapes.Tests
renderer-function-calls.test.ts: options and call validation (including an empty name), duplicate IDs andmaxPending, tuple and named results, cancellation, an emptysurfaceId, ignored responses, a malformed response that fails its pending call, timers cleared on settle, timeouts anddispose,nulldefaultTimeoutMs,resolveas the processor'sonRendererFunctionResponse, and whatresolvereturns for pending and other responses. Fake timers are reset in anafterEachhook.renderer-function-calls.test.ts(conformance) runsconformance/agent/renderer_function_calls.yaml. That suite lands with feat(python-agent): call renderer functions from the agent #3939, so on this branch the runner registers one skipped test saying the suite isn't in the checkout. With the suite from feat(python-agent): call renderer functions from the agent #3939 copied into a copy of this branch, all 30 cases and the non-empty check pass (31 tests), delivering responses throughRendererToAgentMessageProcessorand checkingcalls.isPending.yarn --cwd typescript/agents/a2ui_agent build && yarn --cwd typescript/agents/a2ui_agent test && yarn --cwd typescript/agents/a2ui_agent lint && yarn --cwd typescript/agents/a2ui_agent format:check: 1428 passed, 69 skipped.