think: reconcile incoming messages to fix duplicate orphan assistant rows (#1381)#1396
Merged
threepointone merged 1 commit intomainfrom Apr 26, 2026
Merged
Conversation
…rows (#1381) When a user submits during a streaming tool turn, useAgentChat ships its optimistic in-flight assistant snapshot. With Session's INSERT-OR-IGNORE-by-ID semantics, the client's snapshot (client- generated id, state: input-available) gets persisted as a separate row alongside the eventual server-owned assistant for the same toolCallId. The next turn's convertToModelMessages then produces a malformed Anthropic prompt and the provider rejects it. Fix: - Move message-reconciler.ts from @cloudflare/ai-chat into agents/chat alongside the other shared chat primitives. AIChatAgent's import is the only behavioral change there. - Wire reconcileMessages + resolveToolMergeId into Think's _handleChatRequest before persistence, mirroring AIChatAgent's persistMessages flow. 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. - Add a regression test for the issue's exact scenario (seed a server-owned tool assistant, post the client's optimistic snapshot, assert no duplicate row, server id and output preserved). Plus a test that a stale snapshot gets its tool output upgraded from the server row when ids match. - Update design docs (chat-api.md, think-roadmap.md, think-vs-aichat.md, chat-shared-layer.md) to retract the "Session obviates reconciliation" claim. Made-with: Cursor
🦋 Changeset detectedLatest commit: ddd013a The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 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 |
agents
@cloudflare/ai-chat
@cloudflare/codemode
hono-agents
@cloudflare/shell
@cloudflare/think
@cloudflare/voice
@cloudflare/worker-bundler
commit: |
Merged
threepointone
added a commit
that referenced
this pull request
Apr 26, 2026
Add a new section summarizing four post-#1384 maintenance PRs (#1393–#1396) and their effects on the multi-session assistant plan. Notes include facet bootstrap via explicit FacetStartupOptions.id (#1393) which removes the storage write/setName shim and makes MyAssistant.name resolve natively; the new beforeStep hook and TurnConfig.output passthrough (#1394); SubmitConcurrencyController being moved into agents/chat (#1395); and message-reconciler moved into agents/chat with Think now reconciling incoming messages (#1396). Clarifies that the chat-shared-layer has been incrementally hoisted into agents/chat and highlights the lack of a vitest+workers harness for examples/assistant, recommending a minimal test harness before hoisting useAgentChat.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1381.
Summary
When a user submits during a streaming tool turn,
useAgentChatships its optimistic in-flight assistant snapshot. With Session's INSERT-OR-IGNORE-by-ID semantics, the client's snapshot (client-generated ID,state: "input-available") gets persisted as a separate row alongside the eventual server-owned assistant for the sametoolCallId. The next turn'sconvertToModelMessagesthen produces a malformed Anthropic prompt and the provider rejects it.AIChatAgentalready had the fix viareconcileMessages+resolveToolMergeId; Think did not. This PR:message-reconciler.tsfrom@cloudflare/ai-chatintoagents/chat(the same pattern as the recentSubmitConcurrencyControllerextraction).AIChatAgentonly changes the import path.Think._handleChatRequestbefore persistence. Staleinput-availablesnapshots pick up the server's tool output viamergeServerToolOutputs, and any incoming assistant whosetoolCallIdalready exists on a server row adopts the server's ID so persistence updates the existing row instead of inserting an orphan.input-availablesnapshot with a matching server ID gets its tool output upgraded.design/chat-api.md,design/think-vs-aichat.md,design/think-roadmap.md, anddesign/chat-shared-layer.mdto retract the "Session obviates reconciliation" claim.Test plan
npx vitest --run -c src/chat/__tests__/vitest.config.ts(relocated reconciler tests + sibling unit tests, 193 passing)npx vitest --run -c src/tests/vitest.config.ts src/tests/message-reconciliation.test.ts(new regression tests)npx vitest --run -c src/tests/vitest.config.ts(261 passing)npx vitest --project workers --run --sequence.shuffle false(387 passing)npm run typecheck(75 projects)ReadLintsclean on touched filesMade with Cursor