Files
ycode/scripts/message-order-regression.ts

122 lines
4.4 KiB
TypeScript

import assert from 'node:assert/strict'
import { applyAssistantEvent, getMessageParts } from '../src/shared/message-parts'
import type { ChatEvent, ChatMessage, ToolCallRecord } from '../src/shared/types'
function tool(overrides: Partial<ToolCallRecord> = {}): ToolCallRecord {
return {
id: 'tool-1',
name: 'read_file',
args: { path: 'notes.txt' },
argsRaw: '{"path":"notes.txt"}',
status: 'running',
...overrides
}
}
function reduce(message: ChatMessage, event: ChatEvent): ChatMessage {
return applyAssistantEvent(message, event)
}
function testOrderedPartsAndImmutableUpdates(): void {
const initial: ChatMessage = {
id: 'assistant-1',
role: 'assistant',
content: '',
tools: [],
parts: [],
createdAt: 1,
status: 'streaming'
}
const afterFirstText = reduce(initial, { runId: 'run-1', type: 'text', delta: 'before' })
const afterToolCall = reduce(afterFirstText, { runId: 'run-1', type: 'tool_call', call: tool() })
const afterSecondText = reduce(afterToolCall, { runId: 'run-1', type: 'text', delta: 'after' })
const afterToolResult = reduce(afterSecondText, {
runId: 'run-1',
type: 'tool_result',
call: tool({ status: 'ok', output: 'file contents', summary: 'file contents', durationMs: 12 })
})
const final = reduce(afterToolResult, {
runId: 'run-1',
type: 'done',
turnDurationMs: 22000,
content: 'beforeafter',
tools: [tool({ status: 'ok', output: 'file contents', summary: 'file contents', durationMs: 12 })]
})
assert.deepEqual(final.parts, [
{ type: 'text', text: 'before' },
{ type: 'tool', toolId: 'tool-1' },
{ type: 'text', text: 'after' }
])
assert.equal(final.content, 'beforeafter')
assert.equal(final.status, 'done')
assert.equal(final.turnDurationMs, 22000, 'terminal duration survives the shared live/persistence projection')
assert.equal(final.tools?.length, 1, 'tool updates must not create duplicate tool records')
assert.equal(final.parts?.filter((part) => part.type === 'tool' && part.toolId === 'tool-1').length, 1,
'tool updates must not create duplicate ordered references')
assert.equal(final.tools?.[0].status, 'ok')
assert.equal(final.tools?.[0].output, 'file contents')
assert.equal(initial.content, '', 'reducing events must not mutate the original message')
assert.deepEqual(initial.parts, [], 'reducing events must not mutate the original parts array')
assert.deepEqual(afterFirstText.parts, [{ type: 'text', text: 'before' }],
'later text and tool updates must not mutate prior message snapshots')
assert.equal(afterFirstText.content, 'before')
assert.deepEqual(afterToolResult.parts, [
{ type: 'text', text: 'before' },
{ type: 'tool', toolId: 'tool-1' },
{ type: 'text', text: 'after' }
])
}
function testLegacyFallback(): void {
const legacy: ChatMessage = {
id: 'legacy-1',
role: 'assistant',
content: 'legacy response',
tools: [tool({ id: 'legacy-tool', status: 'ok' })],
createdAt: 2
}
assert.deepEqual(getMessageParts(legacy), [
{ type: 'text', text: 'legacy response' },
{ type: 'tool', toolId: 'legacy-tool' }
])
}
function testTerminalToolFinalization(): void {
const initial: ChatMessage = {
id: 'assistant-terminal',
role: 'assistant',
content: 'partial answer',
tools: [tool({ output: 'retained output' })],
parts: [
{ type: 'text', text: 'partial answer' },
{ type: 'tool', toolId: 'tool-1' }
],
createdAt: 3,
status: 'streaming'
}
const cancelled = reduce(initial, { runId: 'run-cancel', type: 'cancelled' })
assert.equal(cancelled.status, 'cancelled')
assert.equal(cancelled.tools?.[0].status, 'cancelled')
assert.equal(cancelled.tools?.[0].output, 'retained output')
const errored = reduce(initial, { runId: 'run-error', type: 'error', message: 'connection lost' })
assert.equal(errored.status, 'error')
assert.equal(errored.error, 'connection lost')
assert.equal(errored.tools?.[0].status, 'error')
assert.equal(errored.tools?.[0].output, 'retained output')
assert.equal(initial.status, 'streaming', 'terminal updates must leave the prior message unchanged')
assert.equal(initial.tools?.[0].status, 'running', 'terminal updates must leave prior tool state unchanged')
assert.equal(initial.tools?.[0].output, 'retained output')
}
testOrderedPartsAndImmutableUpdates()
testLegacyFallback()
testTerminalToolFinalization()
console.log('message-order-regression: all checks passed')