Patch Changes
-
#1040
766f20bThanks @threepointone! - ChangedwaitForMcpConnectionsdefault fromfalseto{ timeout: 10_000 }. MCP connections are now waited on by default with a 10-second timeout, sogetAITools()returns the full set of tools inonChatMessagewithout requiring explicit opt-in. SetwaitForMcpConnections = falseto restore the previous behavior. -
#1020
70ebb05Thanks @threepointone! - udpate dependencies -
#1013
11aaaffThanks @threepointone! - Fix Gemini "missing thought_signature" error when using client-side tools withaddToolOutput.The server-side message builder (
applyChunkToParts) was droppingproviderMetadatafrom tool-input stream chunks instead of storing it ascallProviderMetadataon tool UIMessage parts. WhenconvertToModelMessageslater read the persisted messages for the continuation call,callProviderMetadatawas undefined, so Gemini never received itsthought_signatureback and rejected the request.- Preserve
callProviderMetadata(mapped from streamproviderMetadata) on tool parts intool-input-start,tool-input-available, andtool-input-errorhandlers — both create and update paths - Preserve
providerExecutedon tool parts (used byconvertToModelMessagesfor provider-executed tools like Gemini code execution) - Preserve
titleon tool parts (tool display name) - Add
providerExecutedtoStreamChunkDatatype explicitly - Add 13 regression tests covering all affected codepaths
- Preserve
-
#989
8404954Thanks @threepointone! - Fix active streams losing UI state after reconnect and dead streams after DO hibernation.- Send
replayCompletesignal after replaying stored chunks for live streams, so the client flushes accumulated parts to React state immediately instead of waiting for the next live chunk. - Detect orphaned streams (restored from SQLite after hibernation with no live LLM reader) via
_isLiveflag onResumableStream. On reconnect, senddone: true, complete the stream, and reconstruct/persist the partial assistant message from stored chunks. - Client-side: flush
activeStreamRefonreplayComplete(keeps stream alive for subsequent live chunks) and ondoneduring replay (finalizes orphaned streams).
- Send
-
#996
baf6751Thanks @threepointone! - Fix race condition where MCP tools are intermittently unavailable in onChatMessage after hibernation.agents: AddedMCPClientManager.waitForConnections(options?)which awaits all in-flight connection and discovery operations. Accepts an optional{ timeout }in milliseconds. Background restore promises fromrestoreConnectionsFromStorage()are now tracked so callers can wait for them to settle.@cloudflare/ai-chat: AddedwaitForMcpConnectionsopt-in config onAIChatAgent. Set totrueto wait indefinitely, or{ timeout: 10_000 }to cap the wait. Default isfalse(non-blocking, preserving existing behavior). For lower-level control, callthis.mcp.waitForConnections()directly in youronChatMessage. -
#993
f706e3fThanks @ferdousbhai! - fix(ai-chat): preserve server tool outputs when client sends approval-responded state_mergeIncomingWithServerStatenow treatsapproval-respondedthe same as
input-availablewhen the server already hasoutput-availablefor a tool call,
preventing stale client state from overwriting completed tool results. -
#1038
e61cb4aThanks @threepointone! - fix(ai-chat): preserve server-generated assistant messages when client appends new messagesThe
_deleteStaleRowsreconciliation inpersistMessagesnow only deletes DB rows when the incoming message set is a subset of the server state (e.g. regenerate trims the conversation). When the client sends new message IDs not yet known to the server, stale deletion is skipped to avoid destroying assistant messages the client hasn't seen. -
#1014
74a3815Thanks @threepointone! - Fixregenerate()leaving stale assistant messages in SQLiteBug 1 — Transport drops
triggerfield:
WebSocketChatTransport.sendMessageswas not including thetriggerfield
(e.g."regenerate-message","submit-message") in the body payload sent
to the server. The AI SDK passes this field so the server can distinguish
between a new message and a regeneration request. Fixed by adding
trigger: options.triggerto the serialized body.On the server side,
triggeris now destructured out of the parsed body
alongsidemessagesandclientTools, so it does not leak into
options.bodyinonChatMessage. Users who inspectoptions.bodywill
not see any change in behavior.Bug 2 —
persistMessagesnever deletes stale rows:
persistMessagesonly performedINSERT ... ON CONFLICT DO UPDATE(upsert),
so whenregenerate()removed the last assistant message from the client's
array, the old row persisted in SQLite. On the next_loadMessagesFromDb,
the stale assistant message reappeared inthis.messages, causing:- Anthropic models to reject with HTTP 400 (conversation must end with a
user message) - Duplicate/phantom assistant messages across reconnects
Fixed by adding an internal
_deleteStaleRowsoption topersistMessages.
When the chat-request handler (CF_AGENT_USE_CHAT_REQUEST) calls
persistMessages, it passes{ _deleteStaleRows: true }, which deletes
any DB rows whose IDs are absent from the incoming (post-merge) message set.
This uses the post-merge IDs from_mergeIncomingWithServerStateto
correctly handle cases where client assistant IDs are remapped to server IDs.The
_deleteStaleRowsflag is internal only (@internalJSDoc) and is
never passed by user code or other handlers (CF_AGENT_CHAT_MESSAGES,
_reply,saveMessages). The default behavior ofpersistMessages
(upsert-only, no deletes) is unchanged.Bug 3 — Content-based reconciliation mismatches identical messages:
_reconcileAssistantIdsWithServerStateused a single-pass cursor for both
exact-ID and content-based matching. When an exact-ID match jumped the
cursor forward, it skipped server messages needed for content matching
of later identical-text assistant messages (e.g. "Sure", "I understand").Rewritten with a two-pass approach: Pass 1 resolves all exact-ID matches
and claims server indices. Pass 2 does content-based matching only over
unclaimed server indices. This prevents exact-ID matches from interfering
with content matching, fixing duplicate rows in long conversations with
repeated short assistant responses. - Anthropic models to reject with HTTP 400 (conversation must end with a
-
#999
95753daThanks @threepointone! - FixuseChatstatusstaying"ready"during stream resumption after page refresh.Four issues prevented stream resumption from working:
- addEventListener race:
onAgentMessagealways handledCF_AGENT_STREAM_RESUMINGbefore the transport's listener, bypassing the AI SDK pipeline. - Transport instance instability:
useMemocreated new transport instances across renders and Strict Mode cycles. When_pkchanged (async queries, socket recreation), the resolver was stranded on the old transport whileonAgentMessagecalledhandleStreamResumingon the new one. - Chat recreation on
_pkchange: Usingagent._pkas theuseChatidcaused the AI SDK to recreate the Chat when the socket changed, abandoning the in-flightmakeRequest(including resume). The resume effect wouldn't re-fire on the new Chat. - Double STREAM_RESUMING: The server sends
STREAM_RESUMINGfrom bothonConnectand theRESUME_REQUESThandler, causing duplicate ACKs and double replay without deduplication.
Fixes:
- Replace
addEventListener-based detection withhandleStreamResuming()— a synchronous methodonAgentMessagecalls directly, eliminating the race. - Make the transport a true singleton (
useRef, created once). Updatetransport.agentevery render so sends/listeners always use the latest socket. The resolver survives_pkchanges because the transport instance never changes. - Use a stable Chat ID (
initialMessagesCacheKeybased on URL + agent + name) instead ofagent._pk, preventing Chat recreation on socket changes. - Add
localRequestIdsRefguard to skip duplicateSTREAM_RESUMINGmessages for streams already handled by the transport.
- addEventListener race:
-
#1029
c898308Thanks @threepointone! - Add experimentalkeepAlive()andkeepAliveWhile()methods to the Agent class. Keeps the Durable Object alive via alarm heartbeats (every 30 seconds), preventing idle eviction during long-running work.keepAlive()returns a disposer function;keepAliveWhile(fn)runs an async function and automatically cleans up the heartbeat when it completes.AIChatAgentnow automatically callskeepAliveWhile()during_reply()streaming, preventing idle eviction during long LLM generations.