@@ -238,24 +238,35 @@ export function formatCollapsedToolPreviewText(value: string | undefined): strin
|
238 | 238 | return normalized.slice(0, 120); |
239 | 239 | } |
240 | 240 | |
241 | | -function findLatestCard(cards: ToolCard[], id: string, name: string): ToolCard | undefined { |
242 | | -for (let i = cards.length - 1; i >= 0; i--) { |
243 | | -const card = cards[i]; |
244 | | -if (!card) { |
245 | | -continue; |
246 | | -} |
247 | | -if (card.id === id || (card.name === name && !card.outputText)) { |
| 241 | +function findFirstUnmatchedCard( |
| 242 | +cards: ToolCard[], |
| 243 | +id: string, |
| 244 | +name: string, |
| 245 | +fallbackMatchedCards: WeakSet<ToolCard>, |
| 246 | +): ToolCard | undefined { |
| 247 | +let nameOnlyCandidate: ToolCard | undefined; |
| 248 | +for (const card of cards) { |
| 249 | +if (card.id === id) { |
248 | 250 | return card; |
249 | 251 | } |
| 252 | +if ( |
| 253 | +!nameOnlyCandidate && |
| 254 | +card.name === name && |
| 255 | +card.outputText === undefined && |
| 256 | +!fallbackMatchedCards.has(card) |
| 257 | +) { |
| 258 | +nameOnlyCandidate = card; |
| 259 | +} |
250 | 260 | } |
251 | | -return undefined; |
| 261 | +return nameOnlyCandidate; |
252 | 262 | } |
253 | 263 | |
254 | 264 | export function extractToolCards(message: unknown, prefix = "tool"): ToolCard[] { |
255 | 265 | const m = message as Record<string, unknown>; |
256 | 266 | const content = normalizeContent(m.content); |
257 | 267 | const messageIsError = readToolErrorFlag(m); |
258 | 268 | const cards: ToolCard[] = []; |
| 269 | +const fallbackMatchedCards = new WeakSet<ToolCard>(); |
259 | 270 | const transcriptMessageId = resolveTranscriptMessageId(m); |
260 | 271 | |
261 | 272 | for (let index = 0; index < content.length; index++) { |
@@ -280,11 +291,12 @@ export function extractToolCards(message: unknown, prefix = "tool"): ToolCard[]
|
280 | 291 | if (kind === "toolresult" || kind === "tool_result") { |
281 | 292 | const name = typeof item.name === "string" ? item.name : "tool"; |
282 | 293 | const cardId = resolveToolCardId(item, m, index, prefix); |
283 | | -const existing = findLatestCard(cards, cardId, name); |
| 294 | +const existing = findFirstUnmatchedCard(cards, cardId, name, fallbackMatchedCards); |
284 | 295 | const text = extractToolText(item); |
285 | 296 | const preview = extractToolPreview(text, name); |
286 | 297 | const isError = readToolErrorFlag(item) ?? messageIsError; |
287 | 298 | if (existing) { |
| 299 | +fallbackMatchedCards.add(existing); |
288 | 300 | existing.outputText = text; |
289 | 301 | existing.preview = preview; |
290 | 302 | if (isError !== undefined) { |
|