Client-Tool Registry and Render Hints#
Overview#
The client-tool registry is the dispatch mechanism that routes browser-fulfilled ("client") tools in the agent chat UI to the correct React widget. It lives in web/oss/src/components/AgentChatSlice/components/clientTools/ and was introduced as part of the initial client-tool round-trip in PR #4934 , then extended with render.kind-based dispatch and the elicitation form in PR #5155 .
The central guarantee the registry provides: a parked client-tool call never causes a run to hang silently. Every unrecognized tool falls through to a generic fallback that auto-settles, allowing the run to resume .
Three-Tier Dispatch Precedence#
registry.tsx resolves the widget for a tool part using three-tier precedence :
| Priority | Key | Example | Handler |
|---|---|---|---|
| 1 | render.kind | "elicitation" | ElicitationWidget |
| 1 | render.kind | "connect" | ConnectToolWidget |
| 2 | toolName | "request_connection" | ConnectToolWidget |
| 3 | (fallback) | any unknown | UnhandledClientTool |
render.kindis the primary dispatch axis — a required wire field on all new interaction kinds. New tools such asrequest_input(the elicitation emitter) are identified exclusively through their render kind .toolNameis the legacy fallback.request_connectionstill resolves by bare name for v1 wire compatibility — the v1 wire predated the render-kind guarantee .UnhandledClientToolis the generic fallback for any tool that matches neither table. It auto-settles the part so the run resumes (see Auto-Settlement).
meta.ts computes the renderKind and toolName properties used by the registry from a ToolUIPart and the message-scoped render map .
Render Hint Wire Transport#
Because AI SDK v6 tool chunks are strict objects that drop inline fields, render.kind cannot travel inline with the tool-call part . Instead it arrives as a sibling data-render message part, a separate part containing a { toolCallId, render } record.
renderMap.ts (added in PR #5155) provides two utilities :
buildRenderMap(parts)— iterates all message parts, filtersdata-renderentries, and returns aMap<toolCallId, RenderHintLike>. This per-message map is the single source of truth for render hints throughout a turn.renderKindFor(part, renderMap)— checks the inlinerender.kindfield first (for future SDK versions), then falls back to the map.
Both the registry (registry.tsx) and the resume predicate (agentApprovalResume.ts) consult the same render map, ensuring consistent tool classification across rendering and resume logic .
AI SDK v6 stream
├─ tool-call part { toolCallId, toolName, args }
└─ data-render part { toolCallId, render: { kind: "elicitation", ... } }
│
▼
buildRenderMap()
│
├──▶ registry.tsx → widget lookup
└──▶ agentApprovalResume.ts → resume predicate
Auto-Settlement and Graceful Degradation#
Two layers of graceful degradation prevent runs from hanging:
Unknown tools — UnhandledClientTool.tsx (introduced in PR #4934) renders a neutral "not handled by this client" surface and immediately auto-settles the part with a non-error output . This means a platform operator can emit a new client-tool kind that the frontend doesn't yet recognize, and the run still progresses.
Invalid payloads — ElicitationWidget.tsx handles malformed elicitation payloads by auto-settling with an errorText prefix ("elicitation: unsupported payload — ..."). A retry-cap flag (degradedEarlierInTurn) prevents the settle→resume→re-emit loop from repeating: a second degradation parks the widget with a visible notice rather than looping indefinitely .
The settlement flow in both cases:
- Widget calls
settle(...)via theonClientToolOutputcallback addToolOutputin the panel matches the parked tool bytoolCallIdagentShouldResumeAfterApprovaldetects the settled result and triggers auto-resume
Resume Predicate and Queue Gating#
agentApprovalResume.ts contains the core logic that integrates render hints into the run-resume decision :
isClientTool(part, renderMap)— classifies a tool part as a client tool whenrenderKindFor()returns a known kind or the tool name is inCLIENT_TOOL_NAMES. This dual check preserves backward compatibility with v1 wire.isClientToolResult(part, renderMap)— identifies a settled client-tool part (output-availableoroutput-error). WhenagentShouldResumeAfterApprovaldetects such a result, it signals immediate auto-resume.
Queue gating — agentMessageQueue.ts uses canReleaseQueuedMessage() to hold the outbound message queue while a pending client-tool interaction exists . This prevents a queued user message from injecting itself into the transcript before the widget has settled, keeping the conversation coherent.
Key guard : An approval-gated tool that was approved and then ran also lands in
output-availablewithproviderExecutedfalsy. The resume predicate must not treat this as a client-tool result; the approval metadata guard is load-bearing.
Unit tests covering the resume predicate and queue gating live in renderMap.test.ts (233 lines, added in PR #5155) and agentApprovalResume.test.ts .
Key Files Reference#
| File | Purpose |
|---|---|
clientTools/registry.tsx | BY_RENDER_KIND and BY_TOOL_NAME dispatch tables; resolution logic |
clientTools/meta.ts | clientToolMeta() — derives renderKind + toolName for a ToolUIPart |
clientTools/types.ts | ClientToolHandler, ClientToolMeta, ClientToolOutputHandler types |
execution/renderMap.ts | buildRenderMap(), renderKindFor() — render-hint extraction and lookup |
execution/agentApprovalResume.ts | Resume predicate; isClientTool(), isClientToolResult() |
execution/agentMessageQueue.ts | Queue gating via canReleaseQueuedMessage() |
clientTools/ElicitationWidget.tsx | Schema-driven inline form; handles degradation + retry cap |
clientTools/ConnectToolWidget.tsx | OAuth connection widget, v1 tool |
clientTools/UnhandledClientTool.tsx | Generic fallback; auto-settles unknown tools |
tests/unit/renderMap.test.ts | 233-line unit suite for render map, resume predicate, queue gating |