withPersistence re-appends stored tool results on an interrupt resume (snapshot ids never match stored id-less tool messages)
Maintainer thường phản hồi trong vòng 1 ngày
Đánh giá
Issue này chưa được đánh giá.
Mô tả
Summary
Resuming a run that parked on a client-tool interrupt, when that run made a server tool call first, saves the tool result twice. withPersistence appends a second copy of every earlier tool result after the interrupted assistant turn. The next provider request is then invalid. Anthropic answers 400 invalid_request_error: messages.N.content.0: unexpected tool_use_id found in tool_result blocks … Each tool_result block must have a corresponding tool_use block in the previous message. The thread stays broken for every later run.
Versions: @tanstack/ai 0.61.0, @tanstack/ai-persistence 0.6.7, @tanstack/ai-client 0.35.1.
Mechanism
- The run calls a server tool (result stored as
{ role: "tool", toolCallId: "call-3" }, noid). It then calls a client tool and stops on the interrupt. - On the interrupt,
buildMessagesSnapshotChunk(activities/chat/index.js) gives id-less model messages a fresh id:snapshot_${runId}_${index}. The browser'sStreamProcessortakes theMESSAGES_SNAPSHOTas its state. - The user answers.
ChatClientpostsuiMessagesToWire(messages), where the stored tool result now carriesid: "snapshot_<run>_2". mergeStoredMessages(stored, incoming)(ai-persistence/src/merge-stored.ts):- The cutoff is the last incoming id found in the store: the interrupted assistant turn.
- The incoming tool message has id
snapshot_…_2, which the store has never seen (its copy has no id). So it is pushed after the prefix: a secondcall-3result, right after the assistant turn that called only the client tool. - Id-less incoming tool messages fall through to the
existing.content === message.contentcheck. It never matches whencontentis a parts array (an MCP result, for example), so those get re-appended too.
Repro (shape)
Server: chat({ messages, middleware: [withPersistence(store)] }) with a scripted adapter:
- step 1: call server tool
load_skill. - step 2: call client tool
ask_user(the run interrupts). - step 3: text.
Browser side, as ChatClient does it:
const processor = new StreamProcessor();
for (const chunk of chunksOfFirstRun) processor.processChunk(chunk); // includes MESSAGES_SNAPSHOT
processor.addToolResult(askUserCallId, answer);
const messages = uiMessagesToWire(processor.getMessages());
// POST { messages, resume: [{ interruptId: `client_tool_${askUserCallId}`, status: "resolved", payload: answer }] }
What the server sees:
posted: [user message-1, assistant message-2, tool(id "snapshot_agui-run-1_2", call-3), assistant message-5, tool(id "tool-call-6", call-6)]
stored: [user message-1, assistant message-2, tool(no id, call-3), assistant message-5]
merged → tool results in order: call-3, call-3, call-6 (expected: call-3, call-6)
Expected
A tool result already stored for a toolCallId is not appended again, whatever id the client gives it. Possible fixes:
- give stored model messages the same ids the snapshot uses;
- or have
mergeStoredMessagesmatch tool messages bytoolCallIdinstead ofid/contentidentity.
Workaround
Before chat(), drop posted role: "tool" messages whose toolCallId the stored thread already answers.
- Ngôn ngữ chính
- TypeScript
- Star
- 3.2k
- Fork
- 372
- Merge trung bình
- 1 ngày 14 giờ
- Pull request đã merge (30 ngày)
- 241
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của TanStack/ai
-
openai-base: a Responses max_output_tokens cutoff ends as RUN_ERROR without usage instead of finishReason 'length'Có thể đã có người làm @pcbeingused333 đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
TanStack/ai#1916 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 64/100
TanStack/ai#1926 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 58/100
TanStack/ai#1924 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
ai-vercel-gateway: usage.cost leaves out gateway surcharges, and the BYOK credential type is not exposedCó thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mở
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 55/100
Maintainer thường phản hồi trong vòng 1 ngày
-
ai-gemini: native image generation throws "returned no id" when modelOptions.store is falseCó thể đã có người làm @pcbeingused333 đã nhận 1 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 57/100
Maintainer thường phản hồi trong vòng 1 ngày
Issue tương tự
-
Broken links in the docsĐang mở
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
liberation-data/drivine#428 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
GitLab: a merge request closed without merging keeps the Git toolbar on View MR instead of Create MRĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
code-yeongyu/oh-my-openagent#11731 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 78/100