mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
* update drift * fix(daemon): typecheck leaf modules * fix(daemon): decode Qoder stdout buffers Generated-By: looper 0.5.6 (runner=fixer, agent=opencode)
249 lines
5.7 KiB
TypeScript
249 lines
5.7 KiB
TypeScript
import { test } from 'vitest';
|
|
import assert from 'node:assert/strict';
|
|
import { Buffer } from 'node:buffer';
|
|
import { createQoderStreamHandler } from '../src/qoder-stream.js';
|
|
|
|
type QoderEvent = Record<string, unknown>;
|
|
|
|
function parseLines(lines: string[]): QoderEvent[] {
|
|
const events: QoderEvent[] = [];
|
|
const handler = createQoderStreamHandler((event) => events.push(event));
|
|
for (const line of lines) {
|
|
handler.feed(`${line}\n`);
|
|
}
|
|
handler.flush();
|
|
return events;
|
|
}
|
|
|
|
test('qoder stream parser maps system init to status', () => {
|
|
const events = parseLines([
|
|
JSON.stringify({
|
|
type: 'system',
|
|
subtype: 'init',
|
|
qodercli_version: '0.2.6',
|
|
model: 'auto',
|
|
session_id: 'session-1',
|
|
}),
|
|
]);
|
|
|
|
assert.deepEqual(events, [
|
|
{
|
|
type: 'status',
|
|
label: 'initializing',
|
|
model: 'auto',
|
|
sessionId: 'session-1',
|
|
qodercliVersion: '0.2.6',
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('qoder stream parser decodes stdout buffer chunks as utf8 JSONL', () => {
|
|
const events: QoderEvent[] = [];
|
|
const handler = createQoderStreamHandler((event) => events.push(event));
|
|
|
|
handler.feed(
|
|
Buffer.from(
|
|
`${JSON.stringify({
|
|
type: 'assistant',
|
|
message: { content: [{ type: 'text', text: 'Buffered output' }] },
|
|
})}\n`,
|
|
'utf8',
|
|
),
|
|
);
|
|
handler.flush();
|
|
|
|
assert.deepEqual(events, [
|
|
{ type: 'text_delta', delta: 'Buffered output' },
|
|
]);
|
|
});
|
|
|
|
test('qoder stream parser maps assistant text content blocks to text deltas', () => {
|
|
const events = parseLines([
|
|
JSON.stringify({
|
|
type: 'assistant',
|
|
message: {
|
|
content: [
|
|
{ type: 'text', text: 'Hello' },
|
|
{ type: 'text', text: ' world' },
|
|
],
|
|
},
|
|
session_id: 'session-1',
|
|
}),
|
|
]);
|
|
|
|
assert.deepEqual(events, [
|
|
{ type: 'text_delta', delta: 'Hello' },
|
|
{ type: 'text_delta', delta: ' world' },
|
|
]);
|
|
});
|
|
|
|
test('qoder stream parser maps assistant errors without text to error events', () => {
|
|
const line = JSON.stringify({
|
|
type: 'assistant',
|
|
message: { content: [] },
|
|
error: { message: 'Qoder authentication expired' },
|
|
});
|
|
const events = parseLines([line]);
|
|
|
|
assert.deepEqual(events, [
|
|
{
|
|
type: 'error',
|
|
message: 'Qoder authentication expired',
|
|
raw: line,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('qoder stream parser uses a fallback message for assistant errors without detail', () => {
|
|
const line = JSON.stringify({
|
|
type: 'assistant',
|
|
message: { content: [] },
|
|
error: { code: 'E_QODER' },
|
|
});
|
|
const events = parseLines([line]);
|
|
|
|
assert.deepEqual(events, [
|
|
{
|
|
type: 'error',
|
|
message: 'Unknown Qoder error',
|
|
raw: line,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('qoder stream parser preserves text from assistant records that also include errors', () => {
|
|
const events = parseLines([
|
|
JSON.stringify({
|
|
type: 'assistant',
|
|
message: {
|
|
content: [{ type: 'text', text: 'Partial answer' }],
|
|
},
|
|
error: { message: 'Trailing Qoder warning' },
|
|
}),
|
|
]);
|
|
|
|
assert.deepEqual(events, [{ type: 'text_delta', delta: 'Partial answer' }]);
|
|
});
|
|
|
|
test('qoder stream parser maps thinking content blocks to thinking events', () => {
|
|
const events = parseLines([
|
|
JSON.stringify({
|
|
type: 'assistant',
|
|
message: {
|
|
content: [
|
|
{
|
|
type: 'thinking',
|
|
thinking: 'Considering the exact response.',
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
]);
|
|
|
|
assert.deepEqual(events, [
|
|
{ type: 'thinking_start' },
|
|
{
|
|
type: 'thinking_delta',
|
|
delta: 'Considering the exact response.',
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('qoder stream parser maps result usage and preserves modelUsage', () => {
|
|
const usage = {
|
|
input_tokens: 10,
|
|
output_tokens: 2,
|
|
service_tier: 'standard',
|
|
};
|
|
const modelUsage = {
|
|
auto: {
|
|
inputTokens: 10,
|
|
outputTokens: 2,
|
|
costUSD: 0,
|
|
},
|
|
};
|
|
const events = parseLines([
|
|
JSON.stringify({
|
|
type: 'result',
|
|
subtype: 'success',
|
|
duration_ms: 10864,
|
|
is_error: false,
|
|
stop_reason: 'end_turn',
|
|
total_cost_usd: 0,
|
|
usage,
|
|
modelUsage,
|
|
}),
|
|
]);
|
|
|
|
assert.deepEqual(events, [
|
|
{
|
|
type: 'usage',
|
|
usage,
|
|
modelUsage,
|
|
costUsd: 0,
|
|
durationMs: 10864,
|
|
stopReason: 'end_turn',
|
|
isError: false,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('qoder stream parser maps result is_error to a fatal error event', () => {
|
|
const usage = {
|
|
input_tokens: 10,
|
|
output_tokens: 2,
|
|
};
|
|
const line = JSON.stringify({
|
|
type: 'result',
|
|
subtype: 'error',
|
|
duration_ms: 10864,
|
|
is_error: true,
|
|
stop_reason: 'tool_use_failed',
|
|
total_cost_usd: 0,
|
|
usage,
|
|
});
|
|
const events = parseLines([line]);
|
|
|
|
assert.deepEqual(events, [
|
|
{
|
|
type: 'usage',
|
|
usage,
|
|
modelUsage: undefined,
|
|
costUsd: 0,
|
|
durationMs: 10864,
|
|
stopReason: 'tool_use_failed',
|
|
isError: true,
|
|
},
|
|
{
|
|
type: 'error',
|
|
message: 'Qoder run failed: tool_use_failed',
|
|
raw: line,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('qoder stream parser forwards unknown and malformed lines as raw events', () => {
|
|
const events = parseLines([
|
|
'{"type":"unknown","value":1}',
|
|
'not json',
|
|
]);
|
|
|
|
assert.deepEqual(events, [
|
|
{ type: 'raw', line: '{"type":"unknown","value":1}' },
|
|
{ type: 'raw', line: 'not json' },
|
|
]);
|
|
});
|
|
|
|
test('qoder stream parser flushes a trailing line without newline', () => {
|
|
const events: QoderEvent[] = [];
|
|
const handler = createQoderStreamHandler((event) => events.push(event));
|
|
handler.feed(
|
|
JSON.stringify({
|
|
type: 'assistant',
|
|
message: { content: [{ type: 'text', text: 'OK' }] },
|
|
}),
|
|
);
|
|
handler.flush();
|
|
|
|
assert.deepEqual(events, [{ type: 'text_delta', delta: 'OK' }]);
|
|
});
|