');
});
});
describe('eventSessionId', () => {
it('reads properties.sessionID for a normal event', () => {
const ev = { type: 'session.idle', properties: { sessionID: 's1' } } as unknown as Event;
expect(eventSessionId(ev)).toBe('s1');
});
it('reads properties.part.sessionID for message.part.updated', () => {
const ev = {
type: 'message.part.updated',
properties: { part: { sessionID: 's2' } },
} as unknown as Event;
expect(eventSessionId(ev)).toBe('s2');
});
it('returns null when there is no session', () => {
const ev = { type: 'server.connected', properties: {} } as unknown as Event;
expect(eventSessionId(ev)).toBeNull();
});
});
describe('resolvePartDedupeKey', () => {
it('prefers the part id', () => {
expect(resolvePartDedupeKey({ id: 'p1', messageID: 'm1' }, 'text')).toBe('text:p1');
});
it('falls back to the message id', () => {
expect(resolvePartDedupeKey({ id: ' ', messageID: 'm1' }, 'reasoning')).toBe('reasoning:message:m1');
});
it('returns null when neither is present', () => {
expect(resolvePartDedupeKey({ id: '', messageID: '' }, 'text')).toBeNull();
});
});
describe('mapToolStatus', () => {
it('maps the opencode tool states to ACP statuses', () => {
expect(mapToolStatus('pending')).toBe('pending');
expect(mapToolStatus('running')).toBe('in_progress');
expect(mapToolStatus('completed')).toBe('completed');
expect(mapToolStatus('error')).toBe('failed');
expect(mapToolStatus(undefined)).toBeNull();
});
});
describe('session.next.tool.* snapshot builders', () => {
it('toolCalledSnapshot → in_progress with tool title + raw input', () => {
expect(toolCalledSnapshot({ callID: 'c1', tool: 'read_file', input: { path: 'a.ts' } })).toEqual({
toolCallId: 'c1',
title: 'read_file',
kind: null,
status: 'in_progress',
rawInput: { path: 'a.ts' },
rawOutput: undefined,
});
});
it('toolSuccessSnapshot → completed with joined text content', () => {
const snap = toolSuccessSnapshot({ callID: 'c1', content: [{ text: 'foo' }, { text: 'bar' }, { other: 1 }] });
expect(snap.status).toBe('completed');
expect(snap.title).toBe('c1');
expect(snap.rawOutput).toBe('foobar');
});
it('toolSuccessSnapshot → empty output when content is missing', () => {
expect(toolSuccessSnapshot({ callID: 'c1' }).rawOutput).toBe('');
});
it('toolFailedSnapshot → failed with stringified error', () => {
const snap = toolFailedSnapshot({ callID: 'c1', error: 'boom' });
expect(snap.status).toBe('failed');
expect(snap.title).toBe('c1');
expect(snap.rawOutput).toBe('boom');
});
});
describe('toolPartToSnapshot', () => {
it('extracts input/output/title/status from the tool state', () => {
const part = {
type: 'tool',
callID: 'c1',
tool: 'grep',
state: { status: 'completed', input: { q: 'x' }, output: 'result', title: 'Grep run' },
} as unknown as Parameters
[0];
expect(toolPartToSnapshot(part)).toEqual({
toolCallId: 'c1',
title: 'Grep run',
kind: null,
status: 'completed',
rawInput: { q: 'x' },
rawOutput: 'result',
});
});
it('falls back to the tool name and uses error as output', () => {
const part = {
type: 'tool',
callID: 'c2',
tool: 'edit',
state: { status: 'error', error: 'nope' },
} as unknown as Parameters[0];
const snap = toolPartToSnapshot(part);
expect(snap.title).toBe('edit');
expect(snap.status).toBe('failed');
expect(snap.rawOutput).toBe('nope');
});
});
describe('classifyPartDelta (message.part.delta dedup recording)', () => {
it('records a reasoning key and emits a reasoning event', () => {
const st = freshDedup();
const e = classifyPartDelta({ partID: 'p1', field: 'reasoning', delta: 'thinking' }, st);
expect(e).toEqual({ type: 'reasoning', text: 'thinking' });
expect(st.streamedPartKeys.has('reasoning:p1')).toBe(true);
});
it('records a text key, strips dcp, and emits text', () => {
const st = freshDedup();
const e = classifyPartDelta({ partID: 'p2', field: 'text', delta: 'hi m' }, st);
expect(e).toEqual({ type: 'text', text: 'hi ' });
expect(st.streamedPartKeys.has('text:p2')).toBe(true);
});
it('still records the text key even when the cleaned delta is empty', () => {
const st = freshDedup();
const e = classifyPartDelta({ partID: 'p3', field: 'text', delta: 'm' }, st);
expect(e).toBeNull();
expect(st.streamedPartKeys.has('text:p3')).toBe(true);
});
it('uses the recorded part type when the field is absent', () => {
const st = freshDedup();
st.partTypeById.set('p4', 'reasoning');
const e = classifyPartDelta({ partID: 'p4', delta: 'more' }, st);
expect(e).toEqual({ type: 'reasoning', text: 'more' });
});
it('returns null for an unknown field', () => {
expect(classifyPartDelta({ partID: 'p5', field: 'other', delta: 'x' }, freshDedup())).toBeNull();
});
});
describe('classifyUpdatedPart (message.part.updated dedup gate)', () => {
function textPart(over: Partial = {}): Part {
return {
type: 'text',
id: 'p1',
messageID: 'm1',
sessionID: 's1',
text: 'final text',
time: { start: 1, end: 2 },
...over,
} as unknown as Part;
}
it('drops a terminal part already streamed via deltas', () => {
const st = freshDedup();
st.streamedPartKeys.add('text:p1');
expect(classifyUpdatedPart(textPart(), st)).toBeNull();
// the key is consumed
expect(st.streamedPartKeys.has('text:p1')).toBe(false);
});
it('emits a finished (ended) text part not seen via deltas', () => {
const st = freshDedup();
expect(classifyUpdatedPart(textPart(), st)).toEqual({ type: 'text', text: 'final text' });
expect(st.partTypeById.get('p1')).toBe('text');
});
it('does not emit a part that has not ended yet', () => {
const st = freshDedup();
expect(classifyUpdatedPart(textPart({ time: { start: 1 } as never }), st)).toBeNull();
});
it('strips dcp tags from the finished text', () => {
const st = freshDedup();
const part = textPart({ text: 'a mb' });
expect(classifyUpdatedPart(part, st)).toEqual({ type: 'text', text: 'a b' });
});
it('maps a running tool part to tool_call', () => {
const st = freshDedup();
const part = { type: 'tool', callID: 'c1', tool: 'grep', state: { status: 'running' } } as unknown as Part;
const e = classifyUpdatedPart(part, st);
expect(e?.type).toBe('tool_call');
});
it('maps a completed tool part to tool_update', () => {
const st = freshDedup();
const part = { type: 'tool', callID: 'c1', tool: 'grep', state: { status: 'completed', output: 'x' } } as unknown as Part;
const e = classifyUpdatedPart(part, st);
expect(e?.type).toBe('tool_update');
});
});
describe('error formatters', () => {
it('errMsg unwraps Error.message', () => {
expect(errMsg(new Error('x'))).toBe('x');
expect(errMsg('plain')).toBe('plain');
});
it('errToString handles null/string/Error/object', () => {
expect(errToString(null)).toBe('unknown error');
expect(errToString('s')).toBe('s');
expect(errToString(new Error('e'))).toBe('e');
expect(errToString({ a: 1 })).toBe('{"a":1}');
});
});