diff --git a/src/lib/components/chat/Messages/OutputEditView.svelte b/src/lib/components/chat/Messages/OutputEditView.svelte index 0fc07373ac..f3a2e4d3f2 100644 --- a/src/lib/components/chat/Messages/OutputEditView.svelte +++ b/src/lib/components/chat/Messages/OutputEditView.svelte @@ -138,7 +138,7 @@ function getMessageText(item: any): string { return (item.content ?? []) - .filter((p: any) => p.type === 'output_text' || 'text' in p) + .filter((p: any) => p && (p.type === 'output_text' || 'text' in p)) .map((p: any) => p.text ?? '') .join('\n'); } @@ -146,7 +146,9 @@ function updateMessageText(idx: number, text: string) { const next = [...output]; const item = { ...next[idx] }; - const parts = (item.content ?? []).filter((p: any) => p.type === 'output_text' || 'text' in p); + const parts = (item.content ?? []).filter( + (p: any) => p && (p.type === 'output_text' || 'text' in p) + ); item.content = [{ ...(parts[0] ?? { type: 'output_text' }), text }]; next[idx] = item; output = next; @@ -155,7 +157,7 @@ function getReasoningText(item: any): string { return (item.summary ?? item.content ?? []) - .filter((p: any) => 'text' in p) + .filter((p: any) => p && 'text' in p) .map((p: any) => p.text ?? '') .join(''); } diff --git a/src/lib/components/chat/Messages/structuredOutput.ts b/src/lib/components/chat/Messages/structuredOutput.ts index d4ebe0493e..612aca3dbe 100644 --- a/src/lib/components/chat/Messages/structuredOutput.ts +++ b/src/lib/components/chat/Messages/structuredOutput.ts @@ -374,7 +374,11 @@ export function buildOutputDisplayItems(output: OutputItem[] = []): OutputDispla }; output.forEach((item, index) => { - if (item?.type === 'function_call_output') { + if (!item) { + return; + } + + if (item.type === 'function_call_output') { const inlineFile = getInlineFileFromToolOutput(toolCallByCallId[item.call_id ?? ''], item); if (inlineFile) { flushDetails(); @@ -388,14 +392,14 @@ export function buildOutputDisplayItems(output: OutputItem[] = []): OutputDispla } if ( - item?.type === 'function_call' && + item.type === 'function_call' && item.name === 'ask_user' && (item.status === 'pending' || item.status === 'in_progress') ) { return; } - if (item?.type && GROUPABLE_OUTPUT_TYPES.has(item.type)) { + if (item.type && GROUPABLE_OUTPUT_TYPES.has(item.type)) { const token = buildDetailToken(item, index === output.length - 1, toolOutputByCallId); if (token) { currentDetailTokens.push(token); @@ -403,7 +407,7 @@ export function buildOutputDisplayItems(output: OutputItem[] = []): OutputDispla return; } - if (item?.type === 'message') { + if (item.type === 'message') { const text = getMessageText(item); if (text.trim()) { flushDetails(); @@ -481,6 +485,17 @@ function ensurePart(parts: OutputContentPart[], index: number, fallback?: Output return parts[index]; } +function setPart( + parts: OutputContentPart[], + index: number, + part: OutputContentPart, + fallback?: OutputContentPart +): void { + // Assigning past the end leaves a hole that later spreads turn into undefined parts. + ensurePart(parts, index, fallback); + parts[index] = part; +} + function findOutputItemIndex(output: OutputItem[], item: OutputItem): number { return output.findIndex( (existing) => @@ -529,7 +544,7 @@ export function applyResponseStreamEvent( } else if (outputIndex < nextOutput.length) { nextOutput.splice(outputIndex, 0, item); } else { - nextOutput[outputIndex] = item; + nextOutput.push(item); } return nextOutput; } @@ -540,7 +555,13 @@ export function applyResponseStreamEvent( } const item = { ...event.item }; const existingIndex = findOutputItemIndex(nextOutput, item); - nextOutput[existingIndex >= 0 ? existingIndex : outputIndex] = item; + if (existingIndex >= 0) { + nextOutput[existingIndex] = item; + } else if (outputIndex < nextOutput.length) { + nextOutput[outputIndex] = item; + } else { + nextOutput.push(item); + } return nextOutput; } @@ -565,7 +586,7 @@ export function applyResponseStreamEvent( return nextOutput; } item.content = [...(item.content ?? [])]; - item.content[event.content_index ?? item.content.length] = { ...event.part }; + setPart(item.content, event.content_index ?? item.content.length, { ...event.part }); return nextOutput; } @@ -574,7 +595,8 @@ export function applyResponseStreamEvent( return nextOutput; } item.summary = [...(item.summary ?? [])]; - item.summary[event.summary_index ?? item.summary.length] = { ...event.part }; + const summaryIndex = event.summary_index ?? item.summary.length; + setPart(item.summary, summaryIndex, { ...event.part }, { type: 'summary_text', text: '' }); return nextOutput; } @@ -604,7 +626,8 @@ export function applyResponseStreamEvent( const typeName = eventType.split('.')[1]; if (typeName === 'content_part' && event.part) { item.content = [...(item.content ?? [])]; - item.content[event.content_index ?? Math.max(item.content.length - 1, 0)] = { ...event.part }; + const contentIndex = event.content_index ?? Math.max(item.content.length - 1, 0); + setPart(item.content, contentIndex, { ...event.part }); } else if (typeName === 'function_call_arguments' && event.arguments !== undefined) { item.arguments = event.arguments; } else if ( @@ -636,7 +659,7 @@ export function replaceOutputMessageText( } const partIndex = item.content.findIndex( - (part) => typeof part.text === 'string' && part.text.includes(oldContent) + (part) => typeof part?.text === 'string' && part.text.includes(oldContent) ); if (partIndex === -1) { return item;