one card per tool call, however often it is announced
a tool's input ARRIVES — the SDK emits tool:start again as more of it streams in — and this appended a new message every time. an AskUserQuestion therefore rendered twice: once half-built, with two of its three options still missing their descriptions, then again complete. the second carried the answer; the first sat above it forever, showing an unanswered question that could not be answered. upsert on toolCallId instead, and only when there is one — a call with no id cannot be recognised later, and collapsing those on anything else would merge two genuinely different calls. the existing message's other fields survive: `output` arrives on tool:result, and a late tool:start must not erase it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
@@ -456,10 +456,32 @@ export function useChat(initialSessionId?: string, initialModel?: string | null,
|
||||
toolInput: msg.toolInput,
|
||||
toolCallId: msg.toolCallId,
|
||||
};
|
||||
|
||||
/*
|
||||
One card per tool call, however many times the harness announces it.
|
||||
|
||||
A tool's input ARRIVES — the SDK emits `tool:start` again as more of it streams in — and this
|
||||
appended every time, so an AskUserQuestion showed up twice: once half-built with two of its three
|
||||
options still missing their descriptions, then again complete. The second carried the answer and
|
||||
the first sat above it forever, unanswerable.
|
||||
|
||||
Keyed on `toolCallId`, and only when there is one: a call without an id cannot be recognised
|
||||
later, and collapsing those on anything else would merge two genuinely different calls.
|
||||
*/
|
||||
const upsert = (list: ChatMessage[]): ChatMessage[] => {
|
||||
const at = msg.toolCallId ? list.findIndex((m) => m.role === 'tool' && m.toolCallId === msg.toolCallId) : -1;
|
||||
if (at === -1) return [...list, call];
|
||||
// Keep whatever the earlier event already carried — `output` arrives on `tool:result` and a
|
||||
// late `tool:start` must not erase it.
|
||||
const next = [...list];
|
||||
const existing = next[at]!;
|
||||
if (existing.role !== 'tool') return [...list, call];
|
||||
next[at] = { ...existing, toolName: msg.toolName, toolInput: msg.toolInput };
|
||||
return next;
|
||||
};
|
||||
|
||||
const parent = msg.parentToolUseId;
|
||||
setMessages((prev) =>
|
||||
parent ? (withChildren(prev, parent, (kids) => [...kids, call]) ?? [...prev, call]) : [...prev, call],
|
||||
);
|
||||
setMessages((prev) => (parent ? (withChildren(prev, parent, upsert) ?? upsert(prev)) : upsert(prev)));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user