@cloudflare/ai-chat@0.5.2
cloudflare/agents@cloudflare/ai-chat@0.5.2Apr 27, 2026by github-actions[bot]
AI Summary
This patch release (0.5.2) fixes three significant bugs in the AI chat system: prevents Chat instance recreation when agent.name mutates in place, extracts shared submit concurrency logic to prevent race conditions in both AIChatAgent and Think, and fixes duplicate orphan assistant rows when users submit during streaming tool turns.
Key Highlights
- Fixes Chat instance recreation bug when agent.name mutates in place, preventing orphaned resumeStream and empty assistant replies after mid-stream refresh
- Extracts SubmitConcurrencyController from agents/chat for shared concurrency handling between AIChatAgent and Think agents
- Fixes Think agent race condition where clear could run stale turn between admission and queue registration
- Fixes debounce cancellation to correctly resolve all in-flight waiters instead of overwriting single timer slot
- Fixes duplicate orphan assistant row bug when user submits during streaming tool turn that caused malformed Anthropic prompts
Full Release Notes
### Patch Changes
- [#1374](https://github.com/cloudflare/agents/pull/1374) [`a6e22c3`](https://github.com/cloudflare/agents/commit/a6e22c362668fc295208d0718eae4cf2aa3f792a) Thanks [@threepointone](https://github.com/threepointone)! - Fix `useAgentChat` recreating the AI SDK Chat instance — and orphaning any in-flight `resumeStream` — whenever `agent.name` transitions in place.
The `useAgent({ basePath })` + `static options = { sendIdentityOnConnect: true }` pattern lets the server own the Durable Object instance name. The browser starts with a placeholder (`"default"`), then `useAgent` mutates the agent object's `.name` to the server-assigned value when the identity frame arrives. `useAgentChat` previously included `agent.name` in the stable chat id it passed to `useChat({ id })`, so the transition changed the id and the AI SDK recreated the underlying Chat instance. The useEffect that fires `chatRef.current.resumeStream()` is keyed on the ref object, not the Chat instance, so it does not re-fire on recreation — the resumed stream kept feeding chunks into the orphaned Chat's state while React subscribed to the new Chat's state, so the user saw an empty assistant reply after a mid-stream refresh until the server's final `CF_AGENT_CHAT_MESSAGES` broadcast landed.
`useAgentChat` now distinguishes an in-place `agent.name` mutation from a genuine "consumer switched chats" event by checking the agent object's reference identity:
- same `agent` reference, `name` mutation → not a chat switch; keep the Chat instance stable.
- new `agent` reference → chat switch; recompute the stable chat id so the AI SDK recreates the Chat against the new conversation.
The stable id is also still upgraded once from the identity-only fallback to the URL-resolved key when the WebSocket handshake completes.
Consumers who want to switch chats without remounting should pass a different `agent` object (e.g. a new `useAgent({...})` call with a different `name`). To get a completely fresh Chat (e.g. when mounting a different chat tab), the conventional React pattern — `key={chatId}` on the parent or swapping the subtree — continues to work.
- [#1395](https://github.com/cloudflare/agents/pull/1395) [`63cfae6`](https://github.com/cloudflare/agents/commit/63cfae6345c5ddc54df5e2f78a19097b9b5462ff) Thanks [@threepointone](https://github.com/threepointone)! - Share submit concurrency bookkeeping through `agents/chat` and use it from both chat agents.
This extracts the `latest`/`merge`/`drop`/`debounce` admission state machine into a `SubmitConcurrencyController` exported from `agents/chat`. `AIChatAgent` semantics (including merge persistence) are preserved. `Think` now picks up the same pending-enqueue protection, so an overlapping submit is still detected while an accepted request is between admission and turn queue registration.
Additional fixes:
- `Think` now captures the turn generation immediately after admission and threads it into `_turnQueue.enqueue`, so a clear that lands between admission and queue registration cannot run a stale turn.
- Pending-enqueue tracking is now bound to a release function tied to the controller's reset epoch, so a release from a pre-reset submit can no longer erase a post-reset submit's marker and let a third submit slip through as non-overlapping.
- Debounce cancellation correctly resolves all in-flight waiters instead of overwriting a single timer slot.
- [#1396](https://github.com/cloudflare/agents/pull/1396) [`fdf5a8a`](https://github.com/cloudflare/agents/commit/fdf5a8a99ec1a88ce9096ddec3a9fb2adf6fd4b1) Thanks [@threepointone](https://github.com/threepointone)! - Fix Think persisting a duplicate orphan assistant row when a user submits during a streaming tool turn ([#1381](https://github.com/cloudflare/agents/issues/1381)).
When `useAgentChat` posts an in-flight assistant snapshot it minted optimistically (client-generated ID, `state: "input-available"`), Session's INSERT-OR-IGNORE-by-ID would store it as a separate row alongside the eventual server-owned assistant for the same `toolCallId`. The next turn's `convertToModelMessages` then produced a malformed Anthropic prompt and the provider rejected it.
`reconcileMessages` and `resolveToolMergeId` now live in `agents/chat` and Think runs them in `_handleChatRequest` before persistence. Stale `input-available` snapshots pick up the server's tool output via `mergeServerToolOutputs`, and any incoming assistant whose `toolCallId` already exists on a server row adopts the server's ID so persistence updates the existing row instead of inserting an orphan.
`@cloudflare/ai-chat` keeps its existing reconciler behavior; the only change is that it now imports `reconcileMessages` / `resolveToolMergeId` from `agents/chat` instead of a local file.