Repository navigation
Conversation
…ALID_FUNCTION_CALL The v1.0 specification requires the renderer to reject a callRendererFunction whose callFunction.catalogId is omitted or unregistered with a rendererFunctionResponse carrying the call's functionCallId and code INVALID_FUNCTION_CALL. RpcHandler already does this for both cases, but the v1.0 version adapter validated the whole envelope against the generated union schema first, so an omitted catalogId threw an A2uiValidationError listing every union branch from processMessages/processMessagesAsync, and the agent never got a response. The v1.0 adapter now accepts a callRendererFunction that fails the union only because callFunction.catalogId is absent, validating the rest of the message against the call schema without that requirement, so the call reaches RpcHandler, which answers INVALID_FUNCTION_CALL. There is no fallback to the surface's default catalog. Any other malformed callRendererFunction still throws. Fixes a2ui-project#3529
There was a problem hiding this comment.
Code Review
This pull request updates the 'MessageProcessor' in specification v1.0 to handle 'callRendererFunction' messages that omit 'callFunction.catalogId' by returning a 'rendererFunctionResponse' with an 'INVALID_FUNCTION_CALL' error instead of throwing an 'A2uiValidationError'. To support this, a relaxed schema validation path was introduced in 'V1Point0Adapter'. The feedback suggests using 'Object.hasOwn' instead of the 'in' operator when checking for the presence of 'catalogId' to prevent potential prototype lookup issues.
Address review feedback: avoid the in operator's prototype lookup.
Deriving it at module load read CallRendererFunctionMessageSchema before it was initialized in the minified Angular production build, which failed with "Cannot read properties of undefined (reading 'extend')" on every page. Build it on first use instead.
|
Thanks, the change is tightly scoped and the tests cover the cases. On your question for reviewers: I opened #3596 to settle it at the spec level before this merges. In short, I lean toward the protocol doc, as this PR does. If #3596 goes that way, this PR should also update the blueprint and add a conformance case. One small thing either way: the "malformed otherwise" test drops 🤖 Agent-generated |
|
Thanks Polina, I'll wait for #3596's conclusion before making further changes :) |
|
Fix: #3650 |
When an agent sent
callRendererFunctionwithout acatalogId, web_core threw a validation error and never replied, so the agent waited until it timed out. It now replies with anINVALID_FUNCTION_CALLresponse, as the v1.0 spec says. Fixes #3529.What changes
catalogIdcase changes. Other malformed calls still throw.Details: root cause, fix, tests
Problem. The v1.0 spec (
callRendererFunction,a2ui_protocol.md) sayscallFunction.catalogIdis required, and that if it is omitted or unregistered, the renderer rejects the call withcode: "INVALID_FUNCTION_CALL". It also says the renderer MUST always send arendererFunctionResponsewith the samefunctionCallId.RpcHandleralready rejects both cases correctly. However,V1Point0Adapterfirst checks the whole envelope against the generatedAgentToRendererMessageSchemaunion. WhencatalogIdwas omitted,processMessagesandprocessMessagesAsyncthrew anA2uiValidationErrorthat listed every branch of the union, and nothing reachedoutboundListener. An agent waiting for the response waited until it timed out. An unregisteredcatalogIdwas already handled correctly.Fix.
BaseVersionAdaptergets avalidatePayloadhook. By default it does the sameschema.safeParseas before.V1Point0Adapteroverrides it. When the union check fails andcallFunctionhas nocatalogIdkey, the adapter checks the message again with a schema derived fromCallRendererFunctionMessageSchema. That schema keeps every other rule, including the strict envelope,functionCallIdandFunctionCall, and drops only thecatalogIdrequirement. If that check passes, the call goes toRpcHandler, which returnsINVALID_FUNCTION_CALL("No catalog available for function resolution."). The response carries the originalfunctionCallIdand also goes tooutboundListener.catalogIdthat is present but not a string, or any other malformedcallRendererFunction, still throws, as before. The existing conformance casetest_rpc_call_renderer_function_invalid_payload_missing_call_functionstill passes.catalogId.Tests.
message-processor.test.ts:catalogIdthroughprocessMessagesAsyncand throughprocessMessages: no throw, and exactly onerendererFunctionResponsewith the samefunctionCallIdandINVALID_FUNCTION_CALL, both returned and sent tooutboundListenercatalogIdINVALID_FUNCTION_CALL, on a surface that has a default catalog, which also shows there is no fallback.Not in this PR (parity). Python
a2ui_corebehaves the same way as web_core did before this fix: Pydantic requirescatalogId, andtests/test_rpc.pyasserts theA2uiValidationError. Swift has the same gap, tracked in #3530. Because of that, I did not add a shared conformance case. The TS runner would pass it, but the Python and Swift runners would fail until they are fixed.Tests run (Node 22,
typescript/core/web_core):corepack yarn test: 1066/1066 pass. Conformance: 561 cases pass, 0 fail, including all 15 cases incore/rpc_functions.yaml.corepack yarn format:check: clean.corepack yarn lint: 0 errors (warnings are the existingno-explicit-anyones).Invalid v1.0 message: createSurface: Expected object ...error shown in the issue.Production build. The relaxed schema is built lazily on first use. Building it at module load broke the minified Angular production build (
Cannot read properties of undefined (reading 'extend')).test:closure-compilerintypescript/renderers/angular/a2ui_explorer: 7/7 pass (7/7 fail without the lazy init).Pre-launch Checklist
One time:
For this PR:
If you need help, consider asking for advice on the discussion board.