import assert from "node:assert/strict"; import test from "node:test"; import { reduceStreamUpdate, hydrateStreamState, type StreamItem, type ToolCallItem, type TodoListItem, } from "./packages/app/src/types/stream"; type AgentStreamEventPayload = Parameters[1]; function assistantTimeline(text: string): AgentStreamEventPayload { return { type: "timeline", provider: "claude", item: { type: "assistant_message", text }, }; } function reasoningTimeline(text: string): AgentStreamEventPayload { return { type: "timeline", provider: "claude", item: { type: "reasoning", text }, }; } function toolTimeline( id: string, status: string, raw?: unknown, options?: { callId?: string | null; provider?: "claude" | "codex"; server?: string; tool?: string; displayName?: string; kind?: string } ): AgentStreamEventPayload { const explicitCallIdProvided = options && Object.prototype.hasOwnProperty.call(options, "callId"); const callIdValue = explicitCallIdProvided ? options?.callId === null ? undefined : options?.callId : id; const provider = options?.provider ?? "claude"; return { type: "timeline", provider, item: { type: "tool_call", server: options?.server ?? "terminal", tool: options?.tool ?? id, status, callId: callIdValue, displayName: options?.displayName ?? id, kind: options?.kind ?? "execute", raw, }, }; } function permissionTimeline(id: string, status: string): AgentStreamEventPayload { return { type: "timeline", provider: "claude", item: { type: "tool_call", server: "permission", tool: "permission_request", status, callId: id, displayName: "Permission", kind: "permission", }, }; } function todoTimeline(items: { text: string; completed: boolean }[]): AgentStreamEventPayload { return { type: "timeline", provider: "codex", item: { type: "todo", items, }, }; } function userTimeline(text: string, messageId?: string): AgentStreamEventPayload { return { type: "timeline", provider: "claude", item: { type: "user_message", text, messageId, }, }; } // Test 1: Same updates applied twice should be idempotent function testIdempotentReduction() { const timestamp1 = new Date('2025-01-01T10:00:00Z'); const timestamp2 = new Date('2025-01-01T10:00:01Z'); const timestamp3 = new Date('2025-01-01T10:00:02Z'); // Create a sequence of updates const updates = [ { event: assistantTimeline("Hello! "), timestamp: timestamp2 }, { event: assistantTimeline("How can I help you?"), timestamp: timestamp2 }, { event: reasoningTimeline("Thinking..."), timestamp: timestamp3 }, ]; // Apply updates once const state1 = hydrateStreamState(updates); // Apply same updates again from scratch const state2 = hydrateStreamState(updates); const state1Str = JSON.stringify(state1); const state2Str = JSON.stringify(state2); const assistantMsg = state1.find(item => item.kind === "assistant_message"); assert.strictEqual(state1Str, state2Str, "Hydrated stream should be deterministic"); assert.strictEqual( assistantMsg?.text, "Hello! How can I help you?", "Assistant chunks should concatenate with preserved spacing" ); } // Test 2: Duplicate user messages should not create duplicates function testUserMessageDeduplication() { const timestamp = new Date("2025-01-01T10:00:00Z"); const updates = [ { event: toolTimeline("tool-1", "pending"), timestamp }, { event: toolTimeline("tool-1", "completed"), timestamp }, ]; const state = hydrateStreamState(updates); const toolCalls = state.filter((item) => item.kind === "tool_call"); assert.strictEqual(toolCalls.length, 1, "Pending/completed tool entries should reconcile"); assert.strictEqual(toolCalls[0].payload.source, "agent"); assert.strictEqual(toolCalls[0].payload.data.status, "completed"); } // Test 3: Multiple assistant messages with different IDs function testMultipleMessages() { const timestamp1 = new Date("2025-01-01T10:00:00Z"); const timestamp2 = new Date("2025-01-01T10:00:05Z"); const updates = [ { event: assistantTimeline("First message"), timestamp: timestamp1 }, { event: toolTimeline("tool-2", "pending"), timestamp: timestamp1 }, { event: toolTimeline("tool-2", "failed"), timestamp: timestamp1 }, { event: assistantTimeline("Second message"), timestamp: timestamp2 }, ]; const state = hydrateStreamState(updates); const assistantMessages = state.filter((item) => item.kind === "assistant_message"); assert.strictEqual(assistantMessages.length, 2, "Assistant messages should remain distinct"); assert.strictEqual(assistantMessages[0].text, "First message"); assert.strictEqual(assistantMessages[1].text, "Second message"); } // Test 4: Tool call raw input should survive completion updates function testToolCallInputPreservation() { const timestampStart = new Date("2025-01-01T10:00:00Z"); const timestampFinish = new Date("2025-01-01T10:00:05Z"); const toolCallId = "tool-raw-test"; const toolInput = { type: "mcp_tool_use", tool_use_id: toolCallId, input: { command: "pwd", }, }; const toolResult = { type: "mcp_tool_result", tool_use_id: toolCallId, output: { stdout: "/tmp", }, }; const updates = [ { event: toolTimeline(toolCallId, "pending", toolInput), timestamp: timestampStart }, { event: toolTimeline(toolCallId, "completed", toolResult), timestamp: timestampFinish }, ]; const state = hydrateStreamState(updates); const toolCallEntry = state.find( (item): item is ToolCallItem => item.kind === "tool_call" && item.payload.source === "agent" ); assert.ok(toolCallEntry, "Tool call entry expected after hydration"); const rawPayload = toolCallEntry!.payload.data.raw as unknown; assert.ok(Array.isArray(rawPayload), "Raw payload should contain both input and result entries"); assert.strictEqual(rawPayload[0], toolInput); assert.strictEqual(rawPayload[1], toolResult); } // Test 5: Completed tool calls without status should infer completion for hydrated state function testToolCallStatusInference() { const toolCallId = 'tool-completion'; const timestamp1 = new Date('2025-01-01T10:10:00Z'); const timestamp2 = new Date('2025-01-01T10:10:05Z'); const startEvent: AgentStreamEventPayload = { type: 'timeline', provider: 'claude', item: { type: 'tool_call', server: 'editor', tool: 'read', status: 'pending', callId: toolCallId, raw: { type: 'tool_use', tool_use_id: toolCallId, input: { file_path: 'README.md' } }, }, }; const completionEvent: AgentStreamEventPayload = { type: 'timeline', provider: 'claude', item: { type: 'tool_call', server: 'editor', tool: 'read', callId: toolCallId, raw: { type: 'tool_result', tool_use_id: toolCallId, output: { content: 'Hello world' } }, output: { content: 'Hello world' }, }, }; const state = hydrateStreamState([ { event: startEvent, timestamp: timestamp1 }, { event: completionEvent, timestamp: timestamp2 }, ]); const toolEntry = state.find( (item): item is ToolCallItem => item.kind === 'tool_call' && item.payload.source === 'agent' && item.payload.data.callId === toolCallId ); assert.ok(toolEntry, "Tool entry should exist after hydration"); assert.strictEqual(toolEntry.payload.data.status, 'completed'); assert.strictEqual( (toolEntry.payload.data.result as { content?: string }).content, 'Hello world' ); } function testToolCallStatusInferenceFromRawOnly() { const toolCallId = 'raw-status'; const timestamp = new Date('2025-01-01T10:20:00Z'); const rawEvent: AgentStreamEventPayload = { type: 'timeline', provider: 'claude', item: { type: 'tool_call', server: 'command', tool: 'shell', callId: toolCallId, raw: { type: 'mcp_tool_result', tool_use_id: toolCallId, result: { metadata: { exit_code: 0 } }, }, }, }; const state = hydrateStreamState([{ event: rawEvent, timestamp }]); const toolEntry = state.find( (item): item is ToolCallItem => item.kind === 'tool_call' && item.payload.source === 'agent' ); assert.strictEqual(toolEntry?.payload.data.status, 'completed'); } function testToolCallFailureInferenceFromRaw() { const toolCallId = 'raw-error'; const timestamp = new Date('2025-01-01T10:25:00Z'); const rawEvent: AgentStreamEventPayload = { type: 'timeline', provider: 'claude', item: { type: 'tool_call', server: 'command', tool: 'shell', callId: toolCallId, raw: { type: 'mcp_tool_result', tool_use_id: toolCallId, is_error: true, error: { message: 'Command failed', }, }, }, }; const state = hydrateStreamState([{ event: rawEvent, timestamp }]); const toolEntry = state.find( (item): item is ToolCallItem => item.kind === 'tool_call' && item.payload.source === 'agent' ); assert.strictEqual(toolEntry?.payload.data.status, 'failed'); } function testToolCallLateCallIdReconciliation() { const timestampStart = new Date('2025-01-01T10:15:00Z'); const timestampFinish = new Date('2025-01-01T10:15:05Z'); const updates = [ { event: toolTimeline('late-call', 'pending', { foo: 'bar' }, { callId: null }), timestamp: timestampStart, }, { event: toolTimeline('late-call', 'completed'), timestamp: timestampFinish }, ]; const state = hydrateStreamState(updates); const toolCalls = state.filter( (item): item is ToolCallItem => item.kind === 'tool_call' && item.payload.source === 'agent' ); assert.strictEqual(toolCalls.length, 1, 'late call ids should merge entries'); assert.strictEqual(toolCalls[0].payload.data.status, 'completed'); assert.strictEqual(toolCalls[0].payload.data.callId, 'late-call'); } function testToolCallParsedPayloadHydration() { const timestampStart = new Date('2025-01-01T10:35:00Z'); const timestampFinish = new Date('2025-01-01T10:35:05Z'); const readCallId = 'parsed-read'; const commandCallId = 'parsed-command'; const updates: Array<{ event: AgentStreamEventPayload; timestamp: Date }> = [ { event: { type: 'timeline', provider: 'claude', item: { type: 'tool_call', server: 'editor', tool: 'read_file', status: 'pending', callId: readCallId, raw: { type: 'tool_use', tool_use_id: readCallId, input: { file_path: 'README.md' }, }, input: { file_path: 'README.md' }, }, }, timestamp: timestampStart, }, { event: { type: 'timeline', provider: 'claude', item: { type: 'tool_call', server: 'editor', tool: 'read_file', callId: readCallId, raw: { type: 'tool_result', tool_use_id: readCallId, output: { content: 'Hello world' }, }, output: { content: 'Hello world' }, }, }, timestamp: timestampFinish, }, { event: { type: 'timeline', provider: 'claude', item: { type: 'tool_call', server: 'command', tool: 'shell', status: 'pending', callId: commandCallId, raw: { type: 'tool_use', tool_use_id: commandCallId, input: { command: 'pwd' }, }, input: { command: 'pwd' }, kind: 'execute', }, }, timestamp: timestampStart, }, { event: { type: 'timeline', provider: 'claude', item: { type: 'tool_call', server: 'command', tool: 'shell', callId: commandCallId, raw: { type: 'tool_result', tool_use_id: commandCallId, result: { command: 'pwd', output: '/Users/dev/voice-dev', }, metadata: { exit_code: 0 }, }, output: { result: { command: 'pwd', output: '/Users/dev/voice-dev', }, metadata: { exit_code: 0 }, }, }, }, timestamp: timestampFinish, }, ]; const state = hydrateStreamState(updates); const readEntry = state.find( (item): item is ToolCallItem => item.kind === 'tool_call' && item.payload.source === 'agent' && item.payload.data.callId === readCallId ); const commandEntry = state.find( (item): item is ToolCallItem => item.kind === 'tool_call' && item.payload.source === 'agent' && item.payload.data.callId === commandCallId ); const readPass = Boolean( readEntry?.payload.data.parsedReads && readEntry.payload.data.parsedReads.length === 1 && readEntry.payload.data.parsedReads[0]?.content.includes('Hello world') ); const commandPass = Boolean( commandEntry?.payload.data.parsedCommand && commandEntry.payload.data.parsedCommand.command === 'pwd' && commandEntry.payload.data.parsedCommand.output?.includes('/voice-dev') ); assert.ok(readPass, 'Read payload should persist across hydration'); assert.ok(commandPass, 'Command payload should persist across hydration'); } function buildClaudeToolUseBlock({ id, name, server, input, }: { id: string; name: string; server: string; input: Record; }) { return { type: 'mcp_tool_use', id, name, server, input, }; } function buildClaudeToolResultBlock({ toolUseId, server, toolName, content, isError, }: { toolUseId: string; server: string; toolName: string; content: Array>; isError?: boolean; }) { return { type: 'mcp_tool_result', tool_use_id: toolUseId, server, tool_name: toolName, is_error: Boolean(isError), content, }; } function testClaudeHydratedToolBodies() { const timestampStart = new Date('2025-01-01T10:40:00Z'); const timestampFinish = new Date('2025-01-01T10:40:05Z'); const editCallId = 'claude-edit-hydration'; const readCallId = 'claude-read-hydration'; const commandCallId = 'claude-command-hydration'; const updates: Array<{ event: AgentStreamEventPayload; timestamp: Date }> = [ { event: { type: 'timeline', provider: 'claude', item: { type: 'tool_call', server: 'editor', tool: 'apply_patch', status: 'pending', callId: editCallId, raw: buildClaudeToolUseBlock({ id: editCallId, name: 'apply_patch', server: 'editor', input: { file_path: 'src/example.ts', patch: '*** Begin Patch...', }, }), }, }, timestamp: timestampStart, }, { event: { type: 'timeline', provider: 'claude', item: { type: 'tool_call', server: 'editor', tool: 'apply_patch', callId: editCallId, raw: buildClaudeToolResultBlock({ toolUseId: editCallId, server: 'editor', toolName: 'apply_patch', content: [ { type: 'input_json', json: { changes: [ { file_path: 'src/example.ts', previous_content: 'export const answer = 41;\n', content: 'export const answer = 42;\n', }, ], }, }, ], }), output: { changes: [ { file_path: 'src/example.ts', previous_content: 'export const answer = 41;\n', content: 'export const answer = 42;\n', }, ], }, }, }, timestamp: timestampFinish, }, { event: { type: 'timeline', provider: 'claude', item: { type: 'tool_call', server: 'editor', tool: 'read_file', status: 'pending', callId: readCallId, raw: buildClaudeToolUseBlock({ id: readCallId, name: 'read_file', server: 'editor', input: { file_path: 'README.md' }, }), }, }, timestamp: timestampStart, }, { event: { type: 'timeline', provider: 'claude', item: { type: 'tool_call', server: 'editor', tool: 'read_file', callId: readCallId, raw: buildClaudeToolResultBlock({ toolUseId: readCallId, server: 'editor', toolName: 'read_file', content: [ { type: 'input_text', text: '# Hydrated test file\nHello Claude!', }, ], }), output: { content: '# Hydrated test file\nHello Claude!' }, }, }, timestamp: timestampFinish, }, { event: { type: 'timeline', provider: 'claude', item: { type: 'tool_call', server: 'command', tool: 'shell', status: 'pending', callId: commandCallId, raw: buildClaudeToolUseBlock({ id: commandCallId, name: 'shell', server: 'command', input: { command: 'ls' }, }), kind: 'execute', }, }, timestamp: timestampStart, }, { event: { type: 'timeline', provider: 'claude', item: { type: 'tool_call', server: 'command', tool: 'shell', callId: commandCallId, raw: buildClaudeToolResultBlock({ toolUseId: commandCallId, server: 'command', toolName: 'shell', content: [ { type: 'input_json', json: { result: { command: 'ls', output: 'README.md\npackages\n', }, metadata: { exit_code: 0 }, }, }, ], }), output: { result: { command: 'ls', output: 'README.md\npackages\n', }, metadata: { exit_code: 0 }, }, }, }, timestamp: timestampFinish, }, ]; const state = hydrateStreamState(updates); const editEntry = state.find( (item): item is ToolCallItem => item.kind === 'tool_call' && item.payload.source === 'agent' && item.payload.data.callId === editCallId ); const readEntry = state.find( (item): item is ToolCallItem => item.kind === 'tool_call' && item.payload.source === 'agent' && item.payload.data.callId === readCallId ); const commandEntry = state.find( (item): item is ToolCallItem => item.kind === 'tool_call' && item.payload.source === 'agent' && item.payload.data.callId === commandCallId ); const editHasDiff = Boolean( editEntry?.payload.data.parsedEdits && editEntry.payload.data.parsedEdits.length > 0 && editEntry.payload.data.parsedEdits[0]?.diffLines.length ); const readHasContent = Boolean( readEntry?.payload.data.parsedReads && readEntry.payload.data.parsedReads.length > 0 && readEntry.payload.data.parsedReads[0]?.content.includes('Hydrated test file') ); const commandHasOutput = Boolean( commandEntry?.payload.data.parsedCommand && commandEntry.payload.data.parsedCommand.command === 'ls' && commandEntry.payload.data.parsedCommand.output?.includes('README.md') ); assert.ok(editHasDiff, 'Edit tool should expose parsed diff payloads'); assert.ok(readHasContent, 'Read tool should expose hydrated file content'); assert.ok(commandHasOutput, 'Command tool should expose hydrated stdout'); } // Test 6: Assistant message chunks should preserve whitespace between words function testAssistantWhitespacePreservation() { const timestamp = new Date('2025-01-01T11:00:00Z'); const updates = [ { event: assistantTimeline("Hello "), timestamp }, { event: assistantTimeline("world"), timestamp }, { event: assistantTimeline(" !"), timestamp }, ]; const state = hydrateStreamState(updates); const assistantMsg = state.find((item) => item.kind === "assistant_message"); assert.strictEqual(assistantMsg?.text, "Hello world !"); } // Test 7: User messages should persist through hydration and deduplicate with live events function testUserMessageHydration() { const timestamp = new Date('2025-01-01T11:30:00Z'); const messageId = 'msg_user_1'; const updates = [ { event: userTimeline('Run npm test', messageId), timestamp }, { event: assistantTimeline('On it!'), timestamp }, ]; const hydrated = hydrateStreamState(updates); const hydratedUser = hydrated.find((item) => item.kind === 'user_message'); assert.strictEqual(hydratedUser?.text, 'Run npm test'); assert.strictEqual(hydratedUser?.id, messageId); const optimisticState: StreamItem[] = [ { kind: 'user_message', id: messageId, text: 'Run npm test', timestamp }, ]; const afterServerEvent = reduceStreamUpdate( optimisticState, userTimeline('Run npm test', messageId), timestamp ); assert.strictEqual(afterServerEvent.length, 1); assert.strictEqual(afterServerEvent[0].kind, 'user_message'); } // Test 8: Permission tool calls should not show in the timeline function testPermissionToolCallFiltering() { const timestamp = new Date('2025-01-01T12:00:00Z'); const updates = [ { event: permissionTimeline('permission-1', 'pending'), timestamp }, { event: permissionTimeline('permission-1', 'granted'), timestamp }, ]; const state = hydrateStreamState(updates); const permissionEntries = state.filter( (item) => item.kind === 'tool_call' && item.payload.source === 'agent' ); assert.strictEqual(permissionEntries.length, 0); } // Test 9: Todo lists should consolidate into a single entry and update completions function testTodoListConsolidation() { const timestamp1 = new Date('2025-01-01T12:30:00Z'); const timestamp2 = new Date('2025-01-01T12:31:00Z'); const firstPlan = [ { text: 'Outline approach', completed: false }, { text: 'Write code', completed: false }, ]; const secondPlan = [ { text: 'Outline approach', completed: true }, { text: 'Write code', completed: false }, ]; const updates = [ { event: todoTimeline(firstPlan), timestamp: timestamp1 }, { event: todoTimeline(secondPlan), timestamp: timestamp2 }, ]; const state = hydrateStreamState(updates); const todoEntries = state.filter( (item): item is TodoListItem => item.kind === 'todo_list' ); assert.strictEqual(todoEntries.length, 1); assert.ok( todoEntries[0].items.some( (entry) => entry.text === 'Outline approach' && entry.completed ), 'Todo entries should consolidate into a single list' ); } function testTimelineIdStabilityAfterRemovals() { const timestamp = new Date('2025-01-01T12:35:00Z'); // Assistant stream entries let assistantState: StreamItem[] = []; for (let i = 0; i < 4; i += 1) { assistantState = reduceStreamUpdate( assistantState, userTimeline(`assistant-prefill-${i}`), timestamp ); } assistantState = reduceStreamUpdate( assistantState, assistantTimeline('Repeatable assistant text'), timestamp ); assistantState = reduceStreamUpdate( assistantState, userTimeline('assistant-separator'), timestamp ); assistantState = assistantState.filter( (item) => !( item.kind === 'user_message' && (item.text === 'assistant-prefill-0' || item.text === 'assistant-prefill-1') ) ); assistantState = reduceStreamUpdate( assistantState, assistantTimeline('Repeatable assistant text'), timestamp ); const assistantIds = assistantState .filter((item) => item.kind === 'assistant_message') .map((item) => item.id); const assistantUnique = new Set(assistantIds); assert.strictEqual( assistantIds.length, assistantUnique.size, 'Assistant ids should stay unique after state shrink' ); // Thought stream entries let thoughtState: StreamItem[] = []; for (let i = 0; i < 3; i += 1) { thoughtState = reduceStreamUpdate( thoughtState, userTimeline(`thought-prefill-${i}`), timestamp ); } thoughtState = reduceStreamUpdate( thoughtState, reasoningTimeline('Repeatable reasoning text'), timestamp ); thoughtState = reduceStreamUpdate( thoughtState, userTimeline('thought-separator'), timestamp ); thoughtState = thoughtState.filter( (item) => !( item.kind === 'user_message' && (item.text === 'thought-prefill-0' || item.text === 'thought-prefill-1') ) ); thoughtState = reduceStreamUpdate( thoughtState, reasoningTimeline('Repeatable reasoning text'), timestamp ); const thoughtIds = thoughtState .filter((item) => item.kind === 'thought') .map((item) => item.id); const thoughtUnique = new Set(thoughtIds); assert.strictEqual( thoughtIds.length, thoughtUnique.size, 'Thought ids should stay unique after state shrink' ); } type ToolCallProvider = 'claude' | 'codex'; function buildConcurrentToolCallUpdates(provider: ToolCallProvider) { const timestamps = [ new Date('2025-01-01T12:40:00Z'), new Date('2025-01-01T12:40:05Z'), new Date('2025-01-01T12:40:10Z'), new Date('2025-01-01T12:40:15Z'), ]; const baseOptions = { provider, server: 'command', tool: 'shell', displayName: 'Run command', kind: 'execute', } as const; return [ { event: toolTimeline( 'shell', 'executing', { type: 'tool_use', provider, step: 'start-1' }, { ...baseOptions, callId: null } ), timestamp: timestamps[0], }, { event: toolTimeline( 'shell', 'executing', { type: 'tool_use', provider, step: 'start-2' }, { ...baseOptions, callId: null } ), timestamp: timestamps[1], }, { event: toolTimeline( 'shell', 'completed', { type: 'tool_result', provider, step: 'finish-1' }, { ...baseOptions, callId: `${provider}-tool-1` } ), timestamp: timestamps[2], }, { event: toolTimeline( 'shell', 'completed', { type: 'tool_result', provider, step: 'finish-2' }, { ...baseOptions, callId: `${provider}-tool-2` } ), timestamp: timestamps[3], }, ]; } function validateToolCallDeduplication( updates: Array<{ event: AgentStreamEventPayload; timestamp: Date }>, mode: 'live' | 'hydrated' ): ToolCallItem[] { const finalState = mode === 'live' ? updates.reduce((state, { event, timestamp }) => { return reduceStreamUpdate(state, event, timestamp); }, []) : hydrateStreamState(updates); return finalState.filter( (item): item is ToolCallItem => item.kind === 'tool_call' && item.payload.source === 'agent' ); } function testToolCallDeduplicationLive() { (['claude', 'codex'] as const).forEach((provider) => { const updates = buildConcurrentToolCallUpdates(provider); const toolCalls = validateToolCallDeduplication(updates, 'live'); const callIds = toolCalls.map((entry) => entry.payload.data.callId).filter(Boolean); const statuses = toolCalls.map((entry) => entry.payload.data.status); assert.strictEqual(toolCalls.length, 2, `${provider} live tool calls should dedupe`); assert.ok( callIds.includes(`${provider}-tool-1`) && callIds.includes(`${provider}-tool-2`), `${provider} live stream should retain tool call identifiers` ); assert.ok( statuses.every((status) => status === 'completed'), `${provider} live stream should mark calls as completed` ); }); } function testToolCallDeduplicationHydrated() { (['claude', 'codex'] as const).forEach((provider) => { const updates = buildConcurrentToolCallUpdates(provider); const toolCalls = validateToolCallDeduplication(updates, 'hydrated'); const callIds = toolCalls.map((entry) => entry.payload.data.callId).filter(Boolean); const statuses = toolCalls.map((entry) => entry.payload.data.status); assert.strictEqual(toolCalls.length, 2, `${provider} hydration should dedupe tool calls`); assert.ok( callIds.includes(`${provider}-tool-1`) && callIds.includes(`${provider}-tool-2`), `${provider} hydration should retain tool call identifiers` ); assert.ok( statuses.every((status) => status === 'completed'), `${provider} hydration should mark calls completed` ); }); } test('Idempotent reduction', testIdempotentReduction); test('Tool call deduplication in-place', testUserMessageDeduplication); test('Multiple assistant messages remain distinct', testMultipleMessages); test('Tool call raw payload preservation', testToolCallInputPreservation); test('Status inferred from completion payloads', testToolCallStatusInference); test('Status inferred from raw exit codes', testToolCallStatusInferenceFromRawOnly); test('Status inferred from raw errors', testToolCallFailureInferenceFromRaw); test('Late call IDs reconcile correctly', testToolCallLateCallIdReconciliation); test('Parsed payload hydration persists', testToolCallParsedPayloadHydration); test('Claude hydration renders tool bodies', testClaudeHydratedToolBodies); test('Assistant whitespace preserved', testAssistantWhitespacePreservation); test('User message hydration/dedup', testUserMessageHydration); test('Permission tool calls filtered', testPermissionToolCallFiltering); test('Todo list consolidation', testTodoListConsolidation); test('Timeline ids stable after removals', testTimelineIdStabilityAfterRemovals); test('Tool call deduplication (live)', testToolCallDeduplicationLive); test('Tool call deduplication (hydrated)', testToolCallDeduplicationHydrated);