Fix client tool schemas lost after DO restart#897
Conversation
🦋 Changeset detectedLatest commit: 14cb9b6 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
…fter reconnect Client tools are sent with each prompt, but _lastClientTools is in-memory only and lost on DO restart. This breaks tool confirmation flows: if the user refreshes before approving a tool, the continuation calls onChatMessage without tool schemas and the AI says it doesn't have the tools. Fix: include clientTools in the CF_AGENT_TOOL_RESULT message. The client is the source of truth for its tools and always has current schemas, so re-sending them is both simpler and more correct than persisting server-side. Related to cloudflare#882.
24fd095 to
14cb9b6
Compare
commit: |
|
/bonk review this PR |
Review of PR #897 — Fix client tool schemas lost after DO restartClean, well-scoped fix for a real problem. The approach of having the client re-send tool schemas with Two issues noted as inline comments below. |
| { | ||
| abortSignal, | ||
| clientTools: this._lastClientTools | ||
| clientTools: clientTools ?? this._lastClientTools |
There was a problem hiding this comment.
Consider also updating _lastClientTools when clientTools arrives via CF_AGENT_TOOL_RESULT.
Currently clientTools is used for this specific continuation but _lastClientTools is never refreshed from it. If this continuation itself produces another tool call (multi-step tool chains), the next continuation falls back to the stale/undefined _lastClientTools — unless the client sends yet another CF_AGENT_TOOL_RESULT with clientTools.
In practice this likely works today because each tool result message from the client includes clientTools, but it would be more robust and self-documenting to also update the cache. Consider adding around line 497, before waitForStream():
// Update cached client tools so subsequent continuations use the latest schemas
if (clientTools?.length) {
this._lastClientTools = clientTools;
}This keeps _lastClientTools in sync regardless of how the tool result arrives, and any future code paths reading it will get the refreshed value.
| name: string; | ||
| description?: string; | ||
| parameters?: Record<string, unknown>; | ||
| }>; |
There was a problem hiding this comment.
Type mismatch with ClientToolSchema: The inline type here uses Record<string, unknown> for parameters, but the canonical ClientToolSchema in index.ts:49 (and react.tsx:66) uses JSONSchema7. This means the wire-format type is looser than what the server actually expects.
This works at runtime (JSON is untyped on the wire), but TypeScript won't catch cases where a non-JSON-Schema object is passed. Two options:
- Use
JSONSchema7here (addimport type { JSONSchema7 } from "ai"totypes.ts) - Reference
ClientToolSchemafromindex.tsinstead of duplicating the shape
Either way avoids the types drifting apart.
Nit-level — not a blocker if you'd prefer to keep types.ts free of ai imports.
|
Review posted on PR #897. Here's a summary of the feedback: Overall: The fix is well-scoped and the approach is sound — having the client re-send tool schemas with Two inline comments posted:
|
|
@alexanderjacobsen this PR should have already the fix for it? #899 give it a go |
|
@whoiskatrin Tested #899 locally and the bug is still there |
|
My bad, this is not related to streaming, just tools. LGTM |
Summary
Follow-up to #882. Client tool schemas (
_lastClientTools) are stored in-memory on the Durable Object and lost on restart. This breaksautoContinueAfterToolResultwhen the user refreshes between a tool call and its confirmation — the continuation callsonChatMessagewithout tool schemas and the AI responds saying it doesn't have the tools.Fix: Include
clientToolsin theCF_AGENT_TOOL_RESULTWebSocket message. The client is the source of truth for its tools and always has the current schemas, so re-sending is both simpler and more correct than server-side persistence.Changes
types.ts— Add optionalclientToolsfield toCF_AGENT_TOOL_RESULTincoming messagereact.tsx— IncludeextractClientToolSchemas()in allCF_AGENT_TOOL_RESULTsends (sendToolOutputToServer, automatic tool resolution,addToolResultAndSendMessage)index.ts— ExtractclientToolsfrom tool result data and pass toonChatMessageduring continuationtests/worker.ts— CaptureclientToolsfromonChatMessageoptions for test assertionstests/client-tools-reconnect.test.ts— Test that tools fromCF_AGENT_TOOL_RESULTare used for continuation, plus backwards compatibility testTest plan
CF_AGENT_TOOL_RESULTare passed toonChatMessageduring continuation (simulates reconnect after DO restart)clientToolsinCF_AGENT_TOOL_RESULTrequiresConfirmationworks after page refresh