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,
|
toolInput: msg.toolInput,
|
||||||
toolCallId: msg.toolCallId,
|
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;
|
const parent = msg.parentToolUseId;
|
||||||
setMessages((prev) =>
|
setMessages((prev) => (parent ? (withChildren(prev, parent, upsert) ?? upsert(prev)) : upsert(prev)));
|
||||||
parent ? (withChildren(prev, parent, (kids) => [...kids, call]) ?? [...prev, call]) : [...prev, call],
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user