|
|
@@ -31,7 +31,6 @@ import {
|
|
|
name,
|
|
|
parseSessionHeader,
|
|
|
parseSessionLog,
|
|
|
- prepareSessionEventNotificationsForComparison,
|
|
|
prepareSessionSnapshotFixtureForComparison,
|
|
|
resolveScriptedEntry,
|
|
|
} from '../src/index.ts'
|
|
|
@@ -399,6 +398,23 @@ describe('fixture format diagnostics', () => {
|
|
|
})
|
|
|
|
|
|
describe('parseSessionLog', () => {
|
|
|
+ const emptySystemHead = {
|
|
|
+ type: 'system/message',
|
|
|
+ seq: 2,
|
|
|
+ time: 0,
|
|
|
+ data: {
|
|
|
+ turn: 1,
|
|
|
+ step: 1,
|
|
|
+ message: {
|
|
|
+ id: expect.stringMatching(/^v2-to-v3-system-/) as unknown,
|
|
|
+ role: 'system',
|
|
|
+ source: { kind: 'plugin', plugin: '@deepseek-ai/dsh-system-prompt' },
|
|
|
+ content: [],
|
|
|
+ },
|
|
|
+ },
|
|
|
+ surfaceOp: 'append',
|
|
|
+ }
|
|
|
+
|
|
|
it('reports invalid JSON at its physical source line', () => {
|
|
|
const header = JSON.stringify({ type: 'session', version: 0, id: 's1', createdAt: 0 })
|
|
|
|
|
|
@@ -419,45 +435,57 @@ describe('parseSessionLog', () => {
|
|
|
|
|
|
it('expands range-encoded source provenance', () => {
|
|
|
const header = JSON.stringify({ type: 'session', version: 0, id: 's1', createdAt: 0 })
|
|
|
- const events = Array.from({ length: 5 }, (_, seq) => ({
|
|
|
- type: 'user/message',
|
|
|
- seq,
|
|
|
- time: 0,
|
|
|
- data: { role: 'user', id: `message-${seq}`, content: [], source: { kind: 'user' } },
|
|
|
- surfaceOp: 'append',
|
|
|
- ...(seq === 4 ? { sourceEventSeqs: [[0, 2], 3] } : {}),
|
|
|
- }))
|
|
|
+ const events = [
|
|
|
+ { type: 'turn/start', seq: 0, time: 0, data: { turn: 1 } },
|
|
|
+ { type: 'step/start', seq: 1, time: 0, data: { turn: 1, step: 1 } },
|
|
|
+ ...Array.from({ length: 5 }, (_, index) => ({
|
|
|
+ type: 'user/message',
|
|
|
+ seq: index + 2,
|
|
|
+ time: 0,
|
|
|
+ data: { role: 'user', id: `message-${index}`, content: [], source: { kind: 'user' } },
|
|
|
+ surfaceOp: 'append',
|
|
|
+ ...(index === 4 ? { sourceEventSeqs: [[2, 4], 5] } : {}),
|
|
|
+ })),
|
|
|
+ ]
|
|
|
const parsed = parseSessionLog(`${header}\n${events.map(event => JSON.stringify(event)).join('\n')}\n`)
|
|
|
- expect(parsed[4]).toEqual({ ...events[4], sourceEventSeqs: [0, 1, 2, 3] })
|
|
|
+ expect(parsed[7]).toEqual({ ...events[6], seq: 7, sourceEventSeqs: [3, 4, 5, 6] })
|
|
|
})
|
|
|
|
|
|
it('reports malformed range provenance with its source line', () => {
|
|
|
const header = JSON.stringify({ type: 'session', version: 0, id: 's1', createdAt: 0 })
|
|
|
- const events = Array.from({ length: 5 }, (_, seq) => ({
|
|
|
- type: 'user/message',
|
|
|
- seq,
|
|
|
- time: 0,
|
|
|
- data: { role: 'user', id: `message-${seq}`, content: [], source: { kind: 'user' } },
|
|
|
- surfaceOp: 'append',
|
|
|
- ...(seq === 4 ? { sourceEventSeqs: [[3, 1]] } : {}),
|
|
|
- }))
|
|
|
+ const events = [
|
|
|
+ { type: 'turn/start', seq: 0, time: 0, data: { turn: 1 } },
|
|
|
+ { type: 'step/start', seq: 1, time: 0, data: { turn: 1, step: 1 } },
|
|
|
+ ...Array.from({ length: 5 }, (_, index) => ({
|
|
|
+ type: 'user/message',
|
|
|
+ seq: index + 2,
|
|
|
+ time: 0,
|
|
|
+ data: { role: 'user', id: `message-${index}`, content: [], source: { kind: 'user' } },
|
|
|
+ surfaceOp: 'append',
|
|
|
+ ...(index === 4 ? { sourceEventSeqs: [[5, 3]] } : {}),
|
|
|
+ })),
|
|
|
+ ]
|
|
|
expect(() => parseSessionLog(`${header}\n${events.map(event => JSON.stringify(event)).join('\n')}\n`))
|
|
|
- .toThrow(/session snapshot line 6: sourceEventSeqs range/)
|
|
|
+ .toThrow(/session snapshot line 8: sourceEventSeqs range/)
|
|
|
})
|
|
|
|
|
|
it('locates malformed range provenance with a materialized v0 seedLength header', () => {
|
|
|
const header = JSON.stringify({ type: 'session', version: 0, id: 's1', createdAt: 0, seedLength: 0 })
|
|
|
- const events = Array.from({ length: 3 }, (_, seq) => ({
|
|
|
- type: 'user/message',
|
|
|
- seq,
|
|
|
- time: 0,
|
|
|
- data: { role: 'user', id: `message-${seq}`, content: [], source: { kind: 'user' } },
|
|
|
- surfaceOp: 'append',
|
|
|
- ...(seq === 2 ? { sourceEventSeqs: [[2, 1]] } : {}),
|
|
|
- }))
|
|
|
+ const events = [
|
|
|
+ { type: 'turn/start', seq: 0, time: 0, data: { turn: 1 } },
|
|
|
+ { type: 'step/start', seq: 1, time: 0, data: { turn: 1, step: 1 } },
|
|
|
+ ...Array.from({ length: 3 }, (_, index) => ({
|
|
|
+ type: 'user/message',
|
|
|
+ seq: index + 2,
|
|
|
+ time: 0,
|
|
|
+ data: { role: 'user', id: `message-${index}`, content: [], source: { kind: 'user' } },
|
|
|
+ surfaceOp: 'append',
|
|
|
+ ...(index === 2 ? { sourceEventSeqs: [[4, 3]] } : {}),
|
|
|
+ })),
|
|
|
+ ]
|
|
|
|
|
|
expect(() => parseSessionLog(`${header}\n${events.map(event => JSON.stringify(event)).join('\n')}\n`))
|
|
|
- .toThrow(/session snapshot line 4: sourceEventSeqs range/)
|
|
|
+ .toThrow(/session snapshot line 6: sourceEventSeqs range/)
|
|
|
})
|
|
|
|
|
|
it('rejects non-object body rows with their source line', () => {
|
|
|
@@ -488,9 +516,9 @@ describe('parseSessionLog', () => {
|
|
|
type: 'text-chunks', seq0: 2, time0: 0,
|
|
|
data: { turn: 1, step: 1, index: 0, dt: [0, 0], texts: ['a', 'b', 'c'] },
|
|
|
})
|
|
|
- expect(parseSessionLog(`${header}\n${turn}\n${step}\n${row}\n`).slice(2)).toEqual([{
|
|
|
+ expect(parseSessionLog(`${header}\n${turn}\n${step}\n${row}\n`).slice(2)).toEqual([emptySystemHead, {
|
|
|
type: 'assistant/attempt',
|
|
|
- seq: 2,
|
|
|
+ seq: 3,
|
|
|
time: 0,
|
|
|
data: {
|
|
|
turn: 1,
|
|
|
@@ -511,9 +539,10 @@ describe('parseSessionLog', () => {
|
|
|
expect(parseSessionLog(`${header}\n${ordinary}\n${step}\n${packed}\n`)).toEqual([
|
|
|
{ type: 'turn/start', seq: 0, time: 0, data: { turn: 1 } },
|
|
|
{ type: 'step/start', seq: 1, time: 0, data: { turn: 1, step: 1 } },
|
|
|
+ emptySystemHead,
|
|
|
{
|
|
|
type: 'assistant/attempt',
|
|
|
- seq: 2,
|
|
|
+ seq: 3,
|
|
|
time: 3,
|
|
|
data: {
|
|
|
turn: 1,
|
|
|
@@ -528,6 +557,7 @@ describe('parseSessionLog', () => {
|
|
|
const source = [
|
|
|
JSON.stringify({ type: 'session', version: 0, id: 'tokens', createdAt: 7, delegationDepth: 0 }),
|
|
|
JSON.stringify({ type: 'turn/start', data: { turn: 1 } }),
|
|
|
+ JSON.stringify({ type: 'step/start', data: { turn: 1, step: 1 } }),
|
|
|
JSON.stringify({
|
|
|
type: 'request/header',
|
|
|
data: {
|
|
|
@@ -537,10 +567,23 @@ describe('parseSessionLog', () => {
|
|
|
}),
|
|
|
].join('\n')
|
|
|
|
|
|
- expect(parseSessionLog(source)[1]).toMatchObject({
|
|
|
- type: 'request/header',
|
|
|
- data: { header: { system: '{{system}}', tools: [] } },
|
|
|
- })
|
|
|
+ expect(parseSessionLog(source).slice(2)).toEqual([
|
|
|
+ emptySystemHead,
|
|
|
+ {
|
|
|
+ ...emptySystemHead,
|
|
|
+ seq: 3,
|
|
|
+ data: {
|
|
|
+ ...emptySystemHead.data,
|
|
|
+ message: { ...emptySystemHead.data.message, content: [{ type: 'text', text: '{{system}}' }] },
|
|
|
+ },
|
|
|
+ surfaceOp: { op: 'replace', start: 2, end: 2 },
|
|
|
+ sourceEventSeqs: [2],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'request/header', seq: 4, time: 0,
|
|
|
+ data: { reason: 'initial', header: { config: { provider: 'mock', model: 'mock' }, tools: [] } },
|
|
|
+ },
|
|
|
+ ])
|
|
|
})
|
|
|
|
|
|
it('materializes Python snapshot tool-name projections before format validation', () => {
|
|
|
@@ -590,9 +633,9 @@ describe('parseSessionLog', () => {
|
|
|
}),
|
|
|
].join('\n')
|
|
|
|
|
|
- expect(parseSessionLog(source).slice(2)).toEqual([{
|
|
|
+ expect(parseSessionLog(source).slice(2)).toEqual([emptySystemHead, {
|
|
|
type: 'assistant/attempt',
|
|
|
- seq: 2,
|
|
|
+ seq: 3,
|
|
|
time: 0,
|
|
|
data: {
|
|
|
turn: 1,
|
|
|
@@ -642,9 +685,9 @@ describe('parseSessionLog', () => {
|
|
|
}),
|
|
|
].join('\n')
|
|
|
|
|
|
- expect(parseSessionLog(source)[2]).toEqual({
|
|
|
+ expect(parseSessionLog(source)[3]).toEqual({
|
|
|
type: 'assistant/message',
|
|
|
- seq: 2,
|
|
|
+ seq: 3,
|
|
|
time: 0,
|
|
|
data: {
|
|
|
turn: 1,
|
|
|
@@ -693,22 +736,34 @@ describe('parseSessionLog', () => {
|
|
|
})
|
|
|
|
|
|
describe('prepareSessionSnapshotFixtureForComparison', () => {
|
|
|
- it('keeps unexpected request-header fields observable after current-format restoration', () => {
|
|
|
+ it('keeps arbitrary future request-header fields observable after current-format restoration', () => {
|
|
|
const source = projectSessionJsonl(replaySessionJsonl([TEXT_CHUNKS]))
|
|
|
const lines = source.trimEnd().split('\n')
|
|
|
const request = {
|
|
|
type: 'request/header',
|
|
|
- data: { reason: 'initial', header: { config: { provider: 'mock', model: 'mock' }, system: 'unexpected prompt' } },
|
|
|
+ data: { reason: 'initial', header: { config: { provider: 'mock', model: 'mock' }, futureHeader: 'unexpected value' } },
|
|
|
}
|
|
|
lines.splice(3, 0, JSON.stringify(request))
|
|
|
const prepared = prepareSessionSnapshotFixtureForComparison(`${lines.join('\n')}\n`)
|
|
|
const restored = prepared.trimEnd().split('\n').map(line => JSON.parse(line) as Record<string, unknown>)
|
|
|
expect(restored.find(record => record.type === 'request/header')).toMatchObject(request)
|
|
|
- delete (request.data.header as Record<string, unknown>).system
|
|
|
+ delete (request.data.header as Record<string, unknown>).futureHeader
|
|
|
lines[3] = JSON.stringify(request)
|
|
|
expect(prepared).not.toEqual(prepareSessionSnapshotFixtureForComparison(`${lines.join('\n')}\n`))
|
|
|
})
|
|
|
|
|
|
+ it('rejects retired request-header system before current-format comparison', () => {
|
|
|
+ const source = projectSessionJsonl(replaySessionJsonl([TEXT_CHUNKS]))
|
|
|
+ const lines = source.trimEnd().split('\n')
|
|
|
+ lines.splice(3, 0, JSON.stringify({
|
|
|
+ type: 'request/header',
|
|
|
+ data: { reason: 'initial', header: { config: { provider: 'mock', model: 'mock' }, system: 'retired prompt' } },
|
|
|
+ }))
|
|
|
+
|
|
|
+ expect(() => prepareSessionSnapshotFixtureForComparison(lines.join('\n')))
|
|
|
+ .toThrow(/request\/header.*system/)
|
|
|
+ })
|
|
|
+
|
|
|
it('encodes a migrated fixture without inventing a trailing newline and retains its cwd token', () => {
|
|
|
const projected = projectSessionJsonl(replaySessionJsonl([TEXT_CHUNKS])).trimEnd()
|
|
|
const [headerLine, ...bodyLines] = projected.split('\n')
|
|
|
@@ -751,211 +806,6 @@ describe('prepareSessionSnapshotFixtureForComparison', () => {
|
|
|
})
|
|
|
})
|
|
|
|
|
|
-describe('prepareSessionEventNotificationsForComparison', () => {
|
|
|
- const wrapHeadlessEvent = (event: Record<string, unknown>) => JSON.stringify({
|
|
|
- type: 'session_event', sessionId: 'session-1', event,
|
|
|
- })
|
|
|
-
|
|
|
- it('migrates a v1 notification tail while retaining non-event protocol rows', () => {
|
|
|
- const events = [
|
|
|
- { type: 'turn/start', seq: 3, time: 0, data: { turn: 1 } },
|
|
|
- { type: 'step/start', seq: 4, time: 0, data: { turn: 1, step: 1 } },
|
|
|
- { type: 'assistant/chunk', seq: 5, time: 5, data: {
|
|
|
- turn: 1, step: 1, chunk: { type: 'block-start', index: 0, blockType: 'text' },
|
|
|
- } },
|
|
|
- { type: 'assistant/chunk', seq: 6, time: 6, data: {
|
|
|
- turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: 'done' },
|
|
|
- } },
|
|
|
- { type: 'assistant/chunk', seq: 7, time: 7, data: {
|
|
|
- turn: 1, step: 1, chunk: { type: 'block-end', index: 0, block: { type: 'text', text: 'done' } },
|
|
|
- } },
|
|
|
- { type: 'assistant/chunk', seq: 8, time: 8, data: {
|
|
|
- turn: 1, step: 1, chunk: { type: 'finish', reason: { kind: 'stop' } },
|
|
|
- } },
|
|
|
- { type: 'assistant/message', seq: 9, time: 9, data: {
|
|
|
- turn: 1,
|
|
|
- step: 1,
|
|
|
- message: {
|
|
|
- role: 'assistant',
|
|
|
- content: [{ type: 'text', text: 'done' }],
|
|
|
- source: { kind: 'model', provider: 'fixture', model: 'fixture' },
|
|
|
- id: 'message-1',
|
|
|
- },
|
|
|
- }, sourceEventSeqs: [5, 6, 7, 8], surfaceOp: 'append' },
|
|
|
- { type: 'step/end', seq: 10, time: 10, data: { turn: 1, step: 1 } },
|
|
|
- { type: 'turn/end', seq: 11, time: 11, data: { turn: 1, reason: { kind: 'completed' } } },
|
|
|
- ]
|
|
|
- const source = [
|
|
|
- ...events.map(event => ({ type: 'session_event', sessionId: 'session-1', event })),
|
|
|
- { type: 'session_event', sessionId: 'session-1', event: {
|
|
|
- type: 'turn/end', seq: 2, time: 12, data: { turn: 1, reason: { kind: 'completed' } },
|
|
|
- } },
|
|
|
- { type: 'session_event', sessionId: 'session-1', event: {
|
|
|
- type: 'feedback/record', seq: 12, time: 13, data: { text: 'resumed parent' },
|
|
|
- } },
|
|
|
- { type: 'result', status: 'completed' },
|
|
|
- ].map(row => JSON.stringify(row)).join('\n') + '\n'
|
|
|
-
|
|
|
- const prepared = prepareSessionEventNotificationsForComparison(source)
|
|
|
- const rows = prepared.trimEnd().split('\n').map(line => JSON.parse(line) as Record<string, unknown>)
|
|
|
- const migrated = rows.flatMap((row) => {
|
|
|
- const event = row['event']
|
|
|
- return event !== null && typeof event === 'object' ? [event] : []
|
|
|
- })
|
|
|
-
|
|
|
- expect(migrated.some(event => 'type' in event && event.type === 'assistant/chunk')).toBe(false)
|
|
|
- expect(migrated.find(event => 'type' in event && event.type === 'assistant/message')).toMatchObject({
|
|
|
- seq: 5,
|
|
|
- data: { stream: expect.any(Array) as unknown },
|
|
|
- })
|
|
|
- expect(rows.at(-1)).toEqual({ type: 'result', status: 'completed' })
|
|
|
- expect(prepared.endsWith('\n')).toBe(true)
|
|
|
- })
|
|
|
-
|
|
|
- it('leaves a current notification stream byte-identical', () => {
|
|
|
- const current = `${JSON.stringify({
|
|
|
- method: 'session.event',
|
|
|
- params: {
|
|
|
- sessionId: 'session-1',
|
|
|
- event: { type: 'assistant/attempt', seq: 0, time: 0, data: { turn: 1, step: 1, stream: [] } },
|
|
|
- },
|
|
|
- })}\n`
|
|
|
-
|
|
|
- expect(prepareSessionEventNotificationsForComparison(current)).toBe(current)
|
|
|
- })
|
|
|
-
|
|
|
- it('omits generation-dependent delivery cursors', () => {
|
|
|
- const source = `${JSON.stringify({
|
|
|
- method: 'session.event',
|
|
|
- params: {
|
|
|
- sessionId: 'session-1',
|
|
|
- event: {
|
|
|
- type: 'session-log-deepseek/delivery-accepted',
|
|
|
- seq: 3,
|
|
|
- time: 0,
|
|
|
- data: { sessionId: 'source-1', sessionFormatVersion: 1, throughSeq: 21 },
|
|
|
- },
|
|
|
- },
|
|
|
- })}\n`
|
|
|
-
|
|
|
- expect(JSON.parse(prepareSessionEventNotificationsForComparison(source))).toMatchObject({
|
|
|
- params: { event: { data: { sessionId: 'source-1' } } },
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- it.each([
|
|
|
- { tools: '{{tools}}', expected: '{{tools}}' },
|
|
|
- { tools: ['read'], expected: [{ name: 'read', description: '', parameters: {} }] },
|
|
|
- ])('migrates a projected request header with tools $tools', ({ tools, expected }) => {
|
|
|
- const events = [
|
|
|
- { type: 'turn/start', seq: 0, time: 0, data: { turn: 1 } },
|
|
|
- { type: 'step/start', seq: 1, time: 0, data: { turn: 1, step: 1 } },
|
|
|
- {
|
|
|
- type: 'request/header',
|
|
|
- seq: 2,
|
|
|
- time: 0,
|
|
|
- data: {
|
|
|
- header: {
|
|
|
- config: { provider: 'fixture', model: 'fixture' },
|
|
|
- system: '{{system}}',
|
|
|
- tools,
|
|
|
- },
|
|
|
- reason: 'initial',
|
|
|
- },
|
|
|
- },
|
|
|
- {
|
|
|
- type: 'assistant/chunk',
|
|
|
- seq: 3,
|
|
|
- time: 0,
|
|
|
- data: { turn: 1, step: 1, chunk: { type: 'block-start', index: 0, blockType: 'text' } },
|
|
|
- },
|
|
|
- { type: 'step/end', seq: 4, time: 0, data: { turn: 1, step: 1 } },
|
|
|
- ]
|
|
|
- const source = events.map(event => JSON.stringify({
|
|
|
- type: 'session_event',
|
|
|
- sessionId: 'session-1',
|
|
|
- event,
|
|
|
- })).join('\n')
|
|
|
-
|
|
|
- const rows = prepareSessionEventNotificationsForComparison(source).split('\n')
|
|
|
- .map(line => JSON.parse(line) as { event: Record<string, unknown> })
|
|
|
- const request = rows.find(row => row.event['type'] === 'request/header') as {
|
|
|
- event: { data: { header: { tools: unknown } } }
|
|
|
- }
|
|
|
-
|
|
|
- expect(request.event.data.header.tools).toEqual(expected)
|
|
|
- expect(rows.some(row => row.event['type'] === 'assistant/chunk')).toBe(false)
|
|
|
- expect(rows.some(row => row.event['type'] === 'assistant/attempt')).toBe(true)
|
|
|
- })
|
|
|
-
|
|
|
- it('assigns an unterminated final chunk group to its last source row', () => {
|
|
|
- const events = [
|
|
|
- { type: 'turn/start', seq: 0, time: 0, data: { turn: 1 } },
|
|
|
- { type: 'step/start', seq: 1, time: 0, data: { turn: 1, step: 1 } },
|
|
|
- {
|
|
|
- type: 'assistant/chunk',
|
|
|
- seq: 2,
|
|
|
- time: 0,
|
|
|
- data: { turn: 1, step: 1, chunk: { type: 'block-start', index: 0, blockType: 'text' } },
|
|
|
- },
|
|
|
- ]
|
|
|
- const source = events.map(event => JSON.stringify({
|
|
|
- method: 'session.event',
|
|
|
- params: { sessionId: 'session-1', event },
|
|
|
- })).join('\n')
|
|
|
-
|
|
|
- const rows = prepareSessionEventNotificationsForComparison(source).split('\n')
|
|
|
- .map(line => JSON.parse(line) as { params: { event: Record<string, unknown> } })
|
|
|
-
|
|
|
- expect(rows).toHaveLength(3)
|
|
|
- expect(rows.at(-1)?.params.event['type']).toBe('assistant/attempt')
|
|
|
- expect(prepareSessionEventNotificationsForComparison('{"type":"result"}')).toBe('{"type":"result"}')
|
|
|
- })
|
|
|
-
|
|
|
- it('keeps malformed SDK wrappers and delivery payloads outside migration unchanged', () => {
|
|
|
- const rows = [
|
|
|
- { method: 'session.event', params: { sessionId: 7, event: {} } },
|
|
|
- { method: 'session.event', params: { sessionId: 'session-1' } },
|
|
|
- {
|
|
|
- method: 'session.event',
|
|
|
- params: {
|
|
|
- sessionId: 'session-1',
|
|
|
- event: { type: 'session-log-deepseek/delivery-accepted', seq: 0, time: 0, data: null },
|
|
|
- },
|
|
|
- },
|
|
|
- ]
|
|
|
- const source = rows.map(row => JSON.stringify(row)).join('\n')
|
|
|
-
|
|
|
- expect(prepareSessionEventNotificationsForComparison(source)).toBe(source)
|
|
|
- })
|
|
|
-
|
|
|
- it('rejects malformed notification rows and invalid v1 tails before migration', () => {
|
|
|
- expect(() => prepareSessionEventNotificationsForComparison('null'))
|
|
|
- .toThrow('session event comparison line 1 must be an object')
|
|
|
-
|
|
|
- expect(() => prepareSessionEventNotificationsForComparison(wrapHeadlessEvent({
|
|
|
- type: 'assistant/chunk', seq: -1, time: 0, data: {},
|
|
|
- }))).toThrow('session event comparison requires a non-negative first seq')
|
|
|
- expect(() => prepareSessionEventNotificationsForComparison([
|
|
|
- wrapHeadlessEvent({ type: 'assistant/chunk', seq: 0, time: 0, data: {} }),
|
|
|
- wrapHeadlessEvent({ type: 7, seq: 1, time: 0, data: {} }),
|
|
|
- ].join('\n'))).toThrow('session event comparison requires a contiguous event tail for session-1')
|
|
|
- })
|
|
|
-
|
|
|
- it('delegates malformed request headers to the released-format validator', () => {
|
|
|
- const source = [
|
|
|
- wrapHeadlessEvent({ type: 'turn/start', seq: 0, time: 0, data: { turn: 1 } }),
|
|
|
- wrapHeadlessEvent({ type: 'request/header', seq: 1, time: 0, data: null }),
|
|
|
- wrapHeadlessEvent({ type: 'assistant/chunk', seq: 2, time: 0, data: {
|
|
|
- turn: 1, step: 1, chunk: { type: 'block-start', index: 0, blockType: 'text' },
|
|
|
- } }),
|
|
|
- ].join('\n')
|
|
|
-
|
|
|
- expect(() => prepareSessionEventNotificationsForComparison(source))
|
|
|
- .toThrow(/request\/header 1 data must be a JSON object/)
|
|
|
- })
|
|
|
-})
|
|
|
-
|
|
|
describe('deriveReplayScript', () => {
|
|
|
it('expands one finished embedded Assistant stream into one replay entry', () => {
|
|
|
const events: SessionEvent[] = [streamEvent(1, 1, 1, TEXT_CHUNKS)]
|
|
|
@@ -1132,25 +982,27 @@ describe('deriveReplayScript', () => {
|
|
|
JSON.stringify({
|
|
|
type: 'session', version: 0, id: 'invalid-compact', createdAt: 0, delegationDepth: 0,
|
|
|
}),
|
|
|
+ JSON.stringify({ type: 'turn/start', seq: 0, time: 0, data: { turn: 1 } }),
|
|
|
+ JSON.stringify({ type: 'step/start', seq: 1, time: 0, data: { turn: 1, step: 1 } }),
|
|
|
JSON.stringify({
|
|
|
- type: 'user/message', seq: 0, time: 0,
|
|
|
+ type: 'user/message', seq: 2, time: 0,
|
|
|
data: { role: 'user', id: 'source', content: [], source: { kind: 'user' } },
|
|
|
surfaceOp: 'append',
|
|
|
}),
|
|
|
JSON.stringify({
|
|
|
- type: 'compaction/start', seq: 1, time: 0,
|
|
|
+ type: 'compaction/start', seq: 3, time: 0,
|
|
|
data: { compactionId: COMPACTION_ID, turn: 1 },
|
|
|
}),
|
|
|
JSON.stringify({
|
|
|
type: 'compaction/summary',
|
|
|
- seq: 2,
|
|
|
+ seq: 4,
|
|
|
time: 0,
|
|
|
data: {
|
|
|
compactionId: COMPACTION_ID,
|
|
|
summary: [{ type: 'text', text: 'missing source events' }],
|
|
|
llmStreamCall: true,
|
|
|
- shadowedRange: { start: 0, end: 0 },
|
|
|
- shadowedSeqs: [0],
|
|
|
+ shadowedRange: { start: 2, end: 2 },
|
|
|
+ shadowedSeqs: [2],
|
|
|
shadowedTokenCount: 20,
|
|
|
provider: 'mock',
|
|
|
model: 'mock',
|