Skip to content

Fix client tool schemas lost after DO restart#897

Merged
whoiskatrin merged 1 commit intocloudflare:mainfrom
alexanderjacobsen:fix/resend-client-tools-on-reconnect
Feb 13, 2026
Merged

Fix client tool schemas lost after DO restart#897
whoiskatrin merged 1 commit intocloudflare:mainfrom
alexanderjacobsen:fix/resend-client-tools-on-reconnect

Conversation

@alexanderjacobsen
Copy link
Contributor

@alexanderjacobsen alexanderjacobsen commented Feb 12, 2026

Summary

Follow-up to #882. Client tool schemas (_lastClientTools) are stored in-memory on the Durable Object and lost on restart. This breaks autoContinueAfterToolResult when the user refreshes between a tool call and its confirmation — the continuation calls onChatMessage without tool schemas and the AI responds saying it doesn't have the tools.

Fix: Include clientTools in the CF_AGENT_TOOL_RESULT WebSocket 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 optional clientTools field to CF_AGENT_TOOL_RESULT incoming message
  • react.tsx — Include extractClientToolSchemas() in all CF_AGENT_TOOL_RESULT sends (sendToolOutputToServer, automatic tool resolution, addToolResultAndSendMessage)
  • index.ts — Extract clientTools from tool result data and pass to onChatMessage during continuation
  • tests/worker.ts — Capture clientTools from onChatMessage options for test assertions
  • tests/client-tools-reconnect.test.ts — Test that tools from CF_AGENT_TOOL_RESULT are used for continuation, plus backwards compatibility test

Test plan

  • All 39 existing tests pass
  • New test: client tools from CF_AGENT_TOOL_RESULT are passed to onChatMessage during continuation (simulates reconnect after DO restart)
  • New test: backwards compatibility — continuation works without clientTools in CF_AGENT_TOOL_RESULT
  • Manual test: tool with requiresConfirmation works after page refresh

@changeset-bot
Copy link

changeset-bot bot commented Feb 12, 2026

🦋 Changeset detected

Latest commit: 14cb9b6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@cloudflare/ai-chat Patch
agents Patch

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.
@alexanderjacobsen alexanderjacobsen force-pushed the fix/resend-client-tools-on-reconnect branch from 24fd095 to 14cb9b6 Compare February 12, 2026 13:17
@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 12, 2026

Open in StackBlitz

npm i https://pkg.pr.new/cloudflare/agents@897

commit: 14cb9b6

@threepointone threepointone self-requested a review February 12, 2026 17:08
@whoiskatrin
Copy link
Contributor

/bonk review this PR

@ask-bonk
Copy link
Contributor

ask-bonk bot commented Feb 13, 2026

Review of PR #897 — Fix client tool schemas lost after DO restart

Clean, well-scoped fix for a real problem. The approach of having the client re-send tool schemas with CF_AGENT_TOOL_RESULT is the right call — the client is the source of truth and this avoids adding server-side persistence complexity. Tests cover both the happy path and backwards compatibility.

Two issues noted as inline comments below.

{
abortSignal,
clientTools: this._lastClientTools
clientTools: clientTools ?? this._lastClientTools
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>;
}>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Use JSONSchema7 here (add import type { JSONSchema7 } from "ai" to types.ts)
  2. Reference ClientToolSchema from index.ts instead 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.

@ask-bonk
Copy link
Contributor

ask-bonk bot commented Feb 13, 2026

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 CF_AGENT_TOOL_RESULT avoids server-side persistence complexity. Tests cover both the reconnect scenario and backwards compatibility.

Two inline comments posted:

  1. packages/ai-chat/src/index.ts:537 — When clientTools arrives via CF_AGENT_TOOL_RESULT, it's used for this continuation but _lastClientTools is never updated. For multi-step tool chains, the next continuation would fall back to stale/undefined _lastClientTools. Suggested adding this._lastClientTools = clientTools to keep the cache in sync.

  2. packages/ai-chat/src/types.ts:129 — The inline clientTools type uses Record<string, unknown> for parameters, while the canonical ClientToolSchema uses JSONSchema7. Minor type consistency nit — not a blocker but avoids the types drifting apart.

github run

@whoiskatrin
Copy link
Contributor

@alexanderjacobsen this PR should have already the fix for it? #899

give it a go

@alexanderjacobsen
Copy link
Contributor Author

@whoiskatrin Tested #899 locally and the bug is still there

@whoiskatrin
Copy link
Contributor

My bad, this is not related to streaming, just tools. LGTM

@whoiskatrin whoiskatrin merged commit 994a808 into cloudflare:main Feb 13, 2026
6 checks passed
@github-actions github-actions bot mentioned this pull request Feb 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants