@agentteams/runner 0.0.120 → 0.0.122
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-client.d.ts +1 -0
- package/dist/api-client.js +16 -0
- package/dist/api-client.js.map +1 -1
- package/dist/handlers/trigger-handler.js +35 -0
- package/dist/handlers/trigger-handler.js.map +1 -1
- package/dist/handlers/trigger-handler.test.js +84 -0
- package/dist/handlers/trigger-handler.test.js.map +1 -1
- package/dist/runners/omp-json-parser.d.ts +52 -0
- package/dist/runners/omp-json-parser.js +255 -0
- package/dist/runners/omp-json-parser.js.map +1 -0
- package/dist/runners/omp-json-parser.test.d.ts +1 -0
- package/dist/runners/omp-json-parser.test.js +169 -0
- package/dist/runners/omp-json-parser.test.js.map +1 -0
- package/dist/runners/omp.d.ts +10 -8
- package/dist/runners/omp.js +53 -27
- package/dist/runners/omp.js.map +1 -1
- package/dist/runners/omp.test.js +111 -5
- package/dist/runners/omp.test.js.map +1 -1
- package/native/bin/win32-x64/agentrunner-launcher.exe +0 -0
- package/native/bin/win32-x64/manifest.json +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses Oh My Pi `--mode json` NDJSON into human-readable log entries, the final assistant text,
|
|
3
|
+
* and the stream-level failure cause. Measured against omp/18.0.6 on 2026-08-29 (macOS arm64);
|
|
4
|
+
* `fixtures/omp-events.jsonl` and `fixtures/omp-events-error.jsonl` are verbatim captures.
|
|
5
|
+
*
|
|
6
|
+
* The stream carries two nesting levels and both must be handled:
|
|
7
|
+
* - top-level lines: `session`, `notice`, `agent_start`, `turn_start`, `message_start`,
|
|
8
|
+
* `message_update`, `message_end`, `turn_end`, `tool_execution_start|update|end`, `agent_end`
|
|
9
|
+
* - `message_update.assistantMessageEvent.type`: `thinking_*`, `text_*`, `toolcall_*`
|
|
10
|
+
*
|
|
11
|
+
* Mirrors opencode-json-parser.ts (parse / line-buffered parser / result capturer) so omp runs are
|
|
12
|
+
* refined the same way instead of dumping raw NDJSON into the trigger log.
|
|
13
|
+
*/
|
|
14
|
+
import { firstSentence, shortenPath } from './stream-json-parser.js';
|
|
15
|
+
const THINKING_PREVIEW_MAX = 300;
|
|
16
|
+
const TOOL_PREVIEW_MAX = 120;
|
|
17
|
+
const COMMAND_PREVIEW_MAX = 100;
|
|
18
|
+
const PATTERN_PREVIEW_MAX = 60;
|
|
19
|
+
const FAILURE_MESSAGE_MAX = 600;
|
|
20
|
+
const STREAMED_TEXT_FALLBACK_MAX = 200_000;
|
|
21
|
+
const COMMAND_ARG_KEYS = ['command', 'cmd', 'script'];
|
|
22
|
+
const PATH_ARG_KEYS = ['path', 'file_path', 'filePath'];
|
|
23
|
+
const PATTERN_ARG_KEYS = ['pattern', 'query'];
|
|
24
|
+
const truncate = (text, max) => (text.length <= max ? text : `${text.slice(0, max)}...`);
|
|
25
|
+
const firstStringArg = (args, keys) => {
|
|
26
|
+
for (const key of keys) {
|
|
27
|
+
const value = args[key];
|
|
28
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return '';
|
|
33
|
+
};
|
|
34
|
+
export const summarizeOmpTool = (toolName, args, cwd) => {
|
|
35
|
+
const safeArgs = args ?? {};
|
|
36
|
+
const command = firstStringArg(safeArgs, COMMAND_ARG_KEYS);
|
|
37
|
+
if (command) {
|
|
38
|
+
const line = command.trim().split(/\r?\n/)[0] ?? '';
|
|
39
|
+
const withoutCwd = cwd && line.includes(cwd) ? line.split(cwd).join('.') : line;
|
|
40
|
+
return `${toolName}: ${truncate(withoutCwd, COMMAND_PREVIEW_MAX)}`;
|
|
41
|
+
}
|
|
42
|
+
const path = firstStringArg(safeArgs, PATH_ARG_KEYS);
|
|
43
|
+
if (path) {
|
|
44
|
+
return `${toolName}: ${shortenPath(path, cwd)}`;
|
|
45
|
+
}
|
|
46
|
+
const pattern = firstStringArg(safeArgs, PATTERN_ARG_KEYS);
|
|
47
|
+
if (pattern) {
|
|
48
|
+
return `${toolName}: "${truncate(pattern, PATTERN_PREVIEW_MAX)}"`;
|
|
49
|
+
}
|
|
50
|
+
const keys = Object.keys(safeArgs).slice(0, 3).join(',');
|
|
51
|
+
return keys ? `${toolName}(${keys})` : toolName;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* `thinking_*`/`text_*`/`toolcall_*` arrive as start → delta* → end. Only the `*_end` event carries
|
|
55
|
+
* the merged `content`, so deltas emit nothing and one entry is logged per completed block.
|
|
56
|
+
* `toolcall_end` is deliberately silent: the same call is logged from the top-level
|
|
57
|
+
* `tool_execution_start`, whose `toolName`/`args` describe what actually ran.
|
|
58
|
+
*/
|
|
59
|
+
const parseAssistantMessageEvent = (event) => {
|
|
60
|
+
const content = typeof event?.content === 'string' ? event.content.trim() : '';
|
|
61
|
+
if (content.length === 0) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
switch (event?.type) {
|
|
65
|
+
case 'thinking_end':
|
|
66
|
+
return [
|
|
67
|
+
{ level: 'INFO', category: 'THINKING', message: `[Thinking] ${truncate(content, THINKING_PREVIEW_MAX)}` },
|
|
68
|
+
];
|
|
69
|
+
case 'text_end':
|
|
70
|
+
return [{ level: 'INFO', category: 'TEXT', message: firstSentence(content) }];
|
|
71
|
+
default:
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const parseOmpEvent = (parsed, options) => {
|
|
76
|
+
switch (parsed.type) {
|
|
77
|
+
case 'session':
|
|
78
|
+
return [{ level: 'INFO', category: 'SYSTEM', message: '[Session started]' }];
|
|
79
|
+
case 'agent_start':
|
|
80
|
+
return [{ level: 'INFO', category: 'SYSTEM', message: '[Agent started]' }];
|
|
81
|
+
case 'turn_start':
|
|
82
|
+
return [{ level: 'INFO', category: 'SYSTEM', message: '[Turn started]' }];
|
|
83
|
+
case 'message_update':
|
|
84
|
+
return parseAssistantMessageEvent(parsed.assistantMessageEvent);
|
|
85
|
+
case 'tool_execution_start': {
|
|
86
|
+
const toolName = parsed.toolName ?? 'unknown';
|
|
87
|
+
const summary = truncate(summarizeOmpTool(toolName, parsed.args, options?.cwd), TOOL_PREVIEW_MAX);
|
|
88
|
+
return [{ level: 'INFO', category: 'TOOL', toolName, message: `[Tool] ${summary}` }];
|
|
89
|
+
}
|
|
90
|
+
case 'tool_execution_end': {
|
|
91
|
+
// omp/18.0.6 puts isError at the event root. Keep the nested check for older producers.
|
|
92
|
+
if (parsed.isError !== true && parsed.result?.isError !== true) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
const toolName = parsed.toolName ?? 'unknown';
|
|
96
|
+
return [{ level: 'WARN', category: 'TOOL', toolName, message: `[Tool] ${toolName} (error)` }];
|
|
97
|
+
}
|
|
98
|
+
default:
|
|
99
|
+
// Unknown top-level types (`notice`, `message_start`, `agent_end`, …) stay out of the log.
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const decodeOmpEvent = (line) => {
|
|
104
|
+
const trimmed = line.trim();
|
|
105
|
+
if (trimmed.length === 0) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
return JSON.parse(trimmed);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
export const parseOmpJsonLine = (line, options) => {
|
|
116
|
+
const parsed = decodeOmpEvent(line);
|
|
117
|
+
return parsed ? parseOmpEvent(parsed, options) : [];
|
|
118
|
+
};
|
|
119
|
+
const createLineScanner = (scan) => {
|
|
120
|
+
let buffer = '';
|
|
121
|
+
return {
|
|
122
|
+
push(chunk) {
|
|
123
|
+
buffer += chunk;
|
|
124
|
+
const lines = buffer.split('\n');
|
|
125
|
+
buffer = lines.pop() ?? '';
|
|
126
|
+
for (const line of lines) {
|
|
127
|
+
scan(line);
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
flush() {
|
|
131
|
+
if (buffer.trim().length > 0) {
|
|
132
|
+
scan(buffer);
|
|
133
|
+
}
|
|
134
|
+
buffer = '';
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Line-buffered parser for chunked omp NDJSON stdout. Chunks split mid-line are held until the
|
|
140
|
+
* newline arrives, and each line is trimmed before parsing so CRLF output loses its trailing `\r`.
|
|
141
|
+
*/
|
|
142
|
+
export const createOmpJsonLineParser = (onEntries, options) => createLineScanner((line) => {
|
|
143
|
+
const entries = parseOmpJsonLine(line, options);
|
|
144
|
+
if (entries.length > 0) {
|
|
145
|
+
onEntries(entries);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
const joinAssistantText = (content) => {
|
|
149
|
+
if (!Array.isArray(content)) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const joined = content
|
|
153
|
+
.filter((part) => part?.type === 'text' && typeof part.text === 'string')
|
|
154
|
+
.map((part) => part.text)
|
|
155
|
+
.join('')
|
|
156
|
+
.trim();
|
|
157
|
+
return joined.length > 0 ? joined : null;
|
|
158
|
+
};
|
|
159
|
+
const composeFailureMessage = (message) => {
|
|
160
|
+
const detail = (message.errorMessage ?? '').trim();
|
|
161
|
+
const status = typeof message.errorStatus === 'number' ? String(message.errorStatus) : '';
|
|
162
|
+
if (detail.length === 0) {
|
|
163
|
+
return status.length > 0
|
|
164
|
+
? `Oh My Pi run failed with status ${status}`
|
|
165
|
+
: 'Oh My Pi run failed before producing an answer';
|
|
166
|
+
}
|
|
167
|
+
return truncate(status.length > 0 && !detail.startsWith(status) ? `${status} ${detail}` : detail, FAILURE_MESSAGE_MAX);
|
|
168
|
+
};
|
|
169
|
+
const createOmpResultState = () => {
|
|
170
|
+
let finalText = null;
|
|
171
|
+
let streamedTextFallback = '';
|
|
172
|
+
let failureMessage = null;
|
|
173
|
+
const appendStreamedText = (text) => {
|
|
174
|
+
const trimmed = text.trim();
|
|
175
|
+
if (trimmed.length === 0 || streamedTextFallback.length >= STREAMED_TEXT_FALLBACK_MAX) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const separator = streamedTextFallback.length > 0 ? '\n' : '';
|
|
179
|
+
const remaining = STREAMED_TEXT_FALLBACK_MAX - streamedTextFallback.length;
|
|
180
|
+
streamedTextFallback += `${separator}${trimmed}`.slice(0, remaining);
|
|
181
|
+
};
|
|
182
|
+
return {
|
|
183
|
+
consume(parsed) {
|
|
184
|
+
if (parsed.type === 'message_update' &&
|
|
185
|
+
parsed.assistantMessageEvent?.type === 'text_end' &&
|
|
186
|
+
typeof parsed.assistantMessageEvent.content === 'string') {
|
|
187
|
+
appendStreamedText(parsed.assistantMessageEvent.content);
|
|
188
|
+
}
|
|
189
|
+
const message = parsed.message;
|
|
190
|
+
if (!message || message.role !== 'assistant') {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
// A run can recover in a later turn. Only the latest terminal event decides failure.
|
|
194
|
+
if (parsed.type === 'message_end' || parsed.type === 'turn_end') {
|
|
195
|
+
failureMessage = message.stopReason === 'error' ? composeFailureMessage(message) : null;
|
|
196
|
+
finalText = joinAssistantText(message.content) ?? finalText;
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
getFinalText: () => finalText,
|
|
200
|
+
getStreamedTextFallback: () => (streamedTextFallback.length > 0 ? streamedTextFallback : null),
|
|
201
|
+
getFailureMessage: () => failureMessage,
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
/**
|
|
205
|
+
* Accumulates the two facts the runner needs from a finished omp stream:
|
|
206
|
+
*
|
|
207
|
+
* - the final assistant text — the last `message_end`/`turn_end` with `role: "assistant"` that
|
|
208
|
+
* carries text content. `role` is also `user` (the echoed prompt) and `toolResult`, so filtering
|
|
209
|
+
* by role is what keeps the prompt out of the answer.
|
|
210
|
+
* - the failure cause — `stopReason: "error"` plus `errorStatus`/`errorMessage`. json mode exits 0
|
|
211
|
+
* even when the provider rejected the request, so this is the only honest failure signal.
|
|
212
|
+
*/
|
|
213
|
+
export const createOmpResultCapturer = () => {
|
|
214
|
+
const state = createOmpResultState();
|
|
215
|
+
const scanner = createLineScanner((line) => {
|
|
216
|
+
const parsed = decodeOmpEvent(line);
|
|
217
|
+
if (parsed) {
|
|
218
|
+
state.consume(parsed);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
return {
|
|
222
|
+
push: scanner.push,
|
|
223
|
+
flush: scanner.flush,
|
|
224
|
+
getFinalText: state.getFinalText,
|
|
225
|
+
getStreamedTextFallback: state.getStreamedTextFallback,
|
|
226
|
+
getFailureMessage: state.getFailureMessage,
|
|
227
|
+
};
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* Scans and decodes each NDJSON line once, then fans the event out to live-log parsing and result
|
|
231
|
+
* capture. Long omp runs can emit tens of thousands of delta lines, so the runner uses this
|
|
232
|
+
* combined consumer instead of maintaining two buffers and calling JSON.parse twice per line.
|
|
233
|
+
*/
|
|
234
|
+
export const createOmpStreamConsumer = (onEntries, options) => {
|
|
235
|
+
const state = createOmpResultState();
|
|
236
|
+
const scanner = createLineScanner((line) => {
|
|
237
|
+
const parsed = decodeOmpEvent(line);
|
|
238
|
+
if (!parsed) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const entries = parseOmpEvent(parsed, options);
|
|
242
|
+
if (entries.length > 0) {
|
|
243
|
+
onEntries(entries);
|
|
244
|
+
}
|
|
245
|
+
state.consume(parsed);
|
|
246
|
+
});
|
|
247
|
+
return {
|
|
248
|
+
push: scanner.push,
|
|
249
|
+
flush: scanner.flush,
|
|
250
|
+
getFinalText: state.getFinalText,
|
|
251
|
+
getStreamedTextFallback: state.getStreamedTextFallback,
|
|
252
|
+
getFailureMessage: state.getFailureMessage,
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
//# sourceMappingURL=omp-json-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"omp-json-parser.js","sourceRoot":"","sources":["../../src/runners/omp-json-parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import test from 'node:test';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { createOmpJsonLineParser, createOmpResultCapturer, parseOmpJsonLine, summarizeOmpTool, } from './omp-json-parser.js';
|
|
6
|
+
const successFixturePath = fileURLToPath(new URL('./fixtures/omp-events.jsonl', import.meta.url));
|
|
7
|
+
const errorFixturePath = fileURLToPath(new URL('./fixtures/omp-events-error.jsonl', import.meta.url));
|
|
8
|
+
const readFixture = (path) => readFile(path, 'utf8');
|
|
9
|
+
const collectEntries = (chunks) => {
|
|
10
|
+
const entries = [];
|
|
11
|
+
const parser = createOmpJsonLineParser((batch) => entries.push(...batch));
|
|
12
|
+
for (const chunk of chunks) {
|
|
13
|
+
parser.push(chunk);
|
|
14
|
+
}
|
|
15
|
+
parser.flush();
|
|
16
|
+
return entries;
|
|
17
|
+
};
|
|
18
|
+
const splitEvery = (text, size) => {
|
|
19
|
+
const chunks = [];
|
|
20
|
+
for (let index = 0; index < text.length; index += size) {
|
|
21
|
+
chunks.push(text.slice(index, index + size));
|
|
22
|
+
}
|
|
23
|
+
return chunks;
|
|
24
|
+
};
|
|
25
|
+
test('parseOmpJsonLine maps the session preamble to SYSTEM entries', () => {
|
|
26
|
+
const session = parseOmpJsonLine('{"type":"session","version":3,"id":"01a0","cwd":"/repo"}');
|
|
27
|
+
assert.deepEqual(session, [{ level: 'INFO', category: 'SYSTEM', message: '[Session started]' }]);
|
|
28
|
+
assert.equal(parseOmpJsonLine('{"type":"agent_start"}')[0]?.category, 'SYSTEM');
|
|
29
|
+
assert.equal(parseOmpJsonLine('{"type":"turn_start"}')[0]?.category, 'SYSTEM');
|
|
30
|
+
});
|
|
31
|
+
test('parseOmpJsonLine emits one entry per completed block and nothing for deltas', () => {
|
|
32
|
+
assert.deepEqual(parseOmpJsonLine('{"type":"message_update","assistantMessageEvent":{"type":"thinking_start"}}'), []);
|
|
33
|
+
assert.deepEqual(parseOmpJsonLine('{"type":"message_update","assistantMessageEvent":{"type":"thinking_delta","delta":"**Plan"}}'), []);
|
|
34
|
+
assert.deepEqual(parseOmpJsonLine('{"type":"message_update","assistantMessageEvent":{"type":"thinking_end","content":"**Plan**"}}'), [{ level: 'INFO', category: 'THINKING', message: '[Thinking] **Plan**' }]);
|
|
35
|
+
assert.deepEqual(parseOmpJsonLine('{"type":"message_update","assistantMessageEvent":{"type":"text_delta","delta":"VER"}}'), []);
|
|
36
|
+
assert.deepEqual(parseOmpJsonLine('{"type":"message_update","assistantMessageEvent":{"type":"text_end","content":"VERIFIED alpha"}}'), [{ level: 'INFO', category: 'TEXT', message: 'VERIFIED alpha' }]);
|
|
37
|
+
});
|
|
38
|
+
test('parseOmpJsonLine logs tool executions with the tool name and summarized args', () => {
|
|
39
|
+
const entries = parseOmpJsonLine('{"type":"tool_execution_start","toolCallId":"call_1","toolName":"write","args":{"path":"/repo/alpha.txt","content":"ALPHA"}}', { cwd: '/repo' });
|
|
40
|
+
assert.deepEqual(entries, [
|
|
41
|
+
{ level: 'INFO', category: 'TOOL', toolName: 'write', message: '[Tool] write: alpha.txt' },
|
|
42
|
+
]);
|
|
43
|
+
// A successful execution was already logged at start; only failures add a second line.
|
|
44
|
+
assert.deepEqual(parseOmpJsonLine('{"type":"tool_execution_end","toolName":"write","result":{"content":[]}}'), []);
|
|
45
|
+
assert.deepEqual(parseOmpJsonLine('{"type":"tool_execution_end","toolName":"write","result":{"content":[],"details":{}},"isError":true}'), [{ level: 'WARN', category: 'TOOL', toolName: 'write', message: '[Tool] write (error)' }]);
|
|
46
|
+
});
|
|
47
|
+
test('parseOmpJsonLine ignores unknown types and unparsable lines', () => {
|
|
48
|
+
// `notice` is emitted once at session start by omp/18.0.6 and is not in the documented type list.
|
|
49
|
+
assert.deepEqual(parseOmpJsonLine('{"type":"notice","level":"info","message":"xd:// mounted","source":"xdev"}'), []);
|
|
50
|
+
assert.deepEqual(parseOmpJsonLine('{"type":"agent_end","messages":[]}'), []);
|
|
51
|
+
assert.deepEqual(parseOmpJsonLine('not json at all'), []);
|
|
52
|
+
assert.deepEqual(parseOmpJsonLine(' '), []);
|
|
53
|
+
});
|
|
54
|
+
test('summarizeOmpTool prefers command, then path, then pattern, then arg keys', () => {
|
|
55
|
+
assert.equal(summarizeOmpTool('bash', { command: 'ls -al\ncd /tmp' }), 'bash: ls -al');
|
|
56
|
+
assert.equal(summarizeOmpTool('read', { path: '/repo/src/index.ts' }, '/repo'), 'read: src/index.ts');
|
|
57
|
+
assert.equal(summarizeOmpTool('grep', { pattern: 'idleTimeout' }), 'grep: "idleTimeout"');
|
|
58
|
+
assert.equal(summarizeOmpTool('todo', { op: 'init', list: [] }), 'todo(op,list)');
|
|
59
|
+
assert.equal(summarizeOmpTool('noargs', undefined), 'noargs');
|
|
60
|
+
});
|
|
61
|
+
test('the success fixture refines into THINKING, TOOL, and TEXT entries', async () => {
|
|
62
|
+
const fixture = await readFixture(successFixturePath);
|
|
63
|
+
const entries = collectEntries([fixture]);
|
|
64
|
+
const categories = entries.map((entry) => entry.category);
|
|
65
|
+
assert.equal(categories.includes('SYSTEM'), true);
|
|
66
|
+
assert.equal(categories.includes('THINKING'), true);
|
|
67
|
+
assert.equal(categories.includes('TOOL'), true);
|
|
68
|
+
assert.equal(categories.includes('TEXT'), true);
|
|
69
|
+
assert.equal(entries.some((entry) => entry.message.trimStart().startsWith('{')), false, 'raw NDJSON must never be forwarded as a log message');
|
|
70
|
+
assert.deepEqual(entries.filter((entry) => entry.category === 'TOOL').map((entry) => entry.toolName), ['todo', 'write', 'read']);
|
|
71
|
+
assert.equal(entries.at(-1)?.category, 'TEXT');
|
|
72
|
+
assert.equal(entries.at(-1)?.message, 'VERIFIED alpha beta gamma');
|
|
73
|
+
});
|
|
74
|
+
test('chunk boundaries in the middle of a line do not change the parsed entries', async () => {
|
|
75
|
+
const fixture = await readFixture(successFixturePath);
|
|
76
|
+
const whole = collectEntries([fixture]);
|
|
77
|
+
for (const size of [1, 7, 64, 997]) {
|
|
78
|
+
assert.deepEqual(collectEntries(splitEvery(fixture, size)), whole, `chunk size ${size} changed the entries`);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
test('CRLF line endings produce the same entries as LF', async () => {
|
|
82
|
+
const fixture = await readFixture(successFixturePath);
|
|
83
|
+
const crlf = fixture.split('\n').join('\r\n');
|
|
84
|
+
assert.deepEqual(collectEntries([crlf]), collectEntries([fixture]));
|
|
85
|
+
assert.deepEqual(collectEntries(splitEvery(crlf, 13)), collectEntries([fixture]));
|
|
86
|
+
});
|
|
87
|
+
test('a trailing line without a newline is parsed on flush', () => {
|
|
88
|
+
const entries = [];
|
|
89
|
+
const parser = createOmpJsonLineParser((batch) => entries.push(...batch));
|
|
90
|
+
parser.push('{"type":"agent_start"}\n{"type":"turn_start"}');
|
|
91
|
+
assert.equal(entries.length, 1);
|
|
92
|
+
parser.flush();
|
|
93
|
+
assert.equal(entries.length, 2);
|
|
94
|
+
});
|
|
95
|
+
test('the result capturer returns the final assistant text and no failure on success', async () => {
|
|
96
|
+
const fixture = await readFixture(successFixturePath);
|
|
97
|
+
const capturer = createOmpResultCapturer();
|
|
98
|
+
for (const chunk of splitEvery(fixture, 11)) {
|
|
99
|
+
capturer.push(chunk);
|
|
100
|
+
}
|
|
101
|
+
capturer.flush();
|
|
102
|
+
assert.equal(capturer.getFinalText(), 'VERIFIED alpha beta gamma');
|
|
103
|
+
assert.equal(capturer.getFailureMessage(), null);
|
|
104
|
+
});
|
|
105
|
+
test('a successful terminal event clears an earlier turn failure', () => {
|
|
106
|
+
const capturer = createOmpResultCapturer();
|
|
107
|
+
capturer.push([
|
|
108
|
+
JSON.stringify({
|
|
109
|
+
type: 'turn_end',
|
|
110
|
+
message: {
|
|
111
|
+
role: 'assistant',
|
|
112
|
+
content: [],
|
|
113
|
+
stopReason: 'error',
|
|
114
|
+
errorStatus: 429,
|
|
115
|
+
errorMessage: 'Rate limited',
|
|
116
|
+
},
|
|
117
|
+
}),
|
|
118
|
+
JSON.stringify({
|
|
119
|
+
type: 'turn_end',
|
|
120
|
+
message: {
|
|
121
|
+
role: 'assistant',
|
|
122
|
+
content: [{ type: 'text', text: 'Recovered answer' }],
|
|
123
|
+
stopReason: 'stop',
|
|
124
|
+
},
|
|
125
|
+
}),
|
|
126
|
+
].join('\n'));
|
|
127
|
+
capturer.flush();
|
|
128
|
+
assert.equal(capturer.getFinalText(), 'Recovered answer');
|
|
129
|
+
assert.equal(capturer.getFailureMessage(), null);
|
|
130
|
+
});
|
|
131
|
+
test('the result capturer keeps completed streamed text before a terminal event', () => {
|
|
132
|
+
const capturer = createOmpResultCapturer();
|
|
133
|
+
capturer.push(`${JSON.stringify({
|
|
134
|
+
type: 'message_update',
|
|
135
|
+
assistantMessageEvent: { type: 'text_end', content: 'Partial but useful answer' },
|
|
136
|
+
})}\n`);
|
|
137
|
+
capturer.flush();
|
|
138
|
+
assert.equal(capturer.getFinalText(), null);
|
|
139
|
+
assert.equal(capturer.getStreamedTextFallback(), 'Partial but useful answer');
|
|
140
|
+
});
|
|
141
|
+
test('the result capturer ignores the echoed user prompt and tool results', async () => {
|
|
142
|
+
const fixture = await readFixture(successFixturePath);
|
|
143
|
+
const capturer = createOmpResultCapturer();
|
|
144
|
+
capturer.push(fixture);
|
|
145
|
+
capturer.flush();
|
|
146
|
+
const finalText = capturer.getFinalText() ?? '';
|
|
147
|
+
assert.equal(finalText.includes('<file name='), false, 'the echoed user prompt must not become the answer');
|
|
148
|
+
assert.equal(finalText.includes('Remaining items'), false, 'a tool result must not become the answer');
|
|
149
|
+
});
|
|
150
|
+
test('the 401 fixture yields the provider failure cause with status and original message', async () => {
|
|
151
|
+
const fixture = await readFixture(errorFixturePath);
|
|
152
|
+
const capturer = createOmpResultCapturer();
|
|
153
|
+
for (const chunk of splitEvery(fixture, 23)) {
|
|
154
|
+
capturer.push(chunk);
|
|
155
|
+
}
|
|
156
|
+
capturer.flush();
|
|
157
|
+
const failure = capturer.getFailureMessage() ?? '';
|
|
158
|
+
assert.match(failure, /401/);
|
|
159
|
+
assert.match(failure, /Incorrect API key provided: sk-bogus/);
|
|
160
|
+
assert.equal(capturer.getFinalText(), null, 'a failed run has no assistant answer');
|
|
161
|
+
});
|
|
162
|
+
test('the failure cause survives CRLF output', async () => {
|
|
163
|
+
const fixture = await readFixture(errorFixturePath);
|
|
164
|
+
const capturer = createOmpResultCapturer();
|
|
165
|
+
capturer.push(fixture.split('\n').join('\r\n'));
|
|
166
|
+
capturer.flush();
|
|
167
|
+
assert.match(capturer.getFailureMessage() ?? '', /401 Incorrect API key provided/);
|
|
168
|
+
});
|
|
169
|
+
//# sourceMappingURL=omp-json-parser.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"omp-json-parser.test.js","sourceRoot":"","sources":["../../src/runners/omp-json-parser.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAG9B,MAAM,kBAAkB,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,6BAA6B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAClG,MAAM,gBAAgB,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,mCAAmC,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAEtG,MAAM,WAAW,GAAG,CAAC,IAAY,EAAmB,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAE9E,MAAM,cAAc,GAAG,CAAC,MAAgB,EAAoB,EAAE;IAC5D,MAAM,OAAO,GAAqB,EAAE,CAAC;IACrC,MAAM,MAAM,GAAG,uBAAuB,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;IAC1E,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IACD,MAAM,CAAC,KAAK,EAAE,CAAC;IACf,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,IAAY,EAAY,EAAE;IAC1D,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,IAAI,EAAE,CAAC;QACvD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF,IAAI,CAAC,8DAA8D,EAAE,GAAG,EAAE;IACxE,MAAM,OAAO,GAAG,gBAAgB,CAAC,0DAA0D,CAAC,CAAC;IAC7F,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC,CAAC,CAAC;IACjG,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,wBAAwB,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAChF,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACjF,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,6EAA6E,EAAE,GAAG,EAAE;IACvF,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,6EAA6E,CAAC,EAAE,EAAE,CAAC,CAAC;IACtH,MAAM,CAAC,SAAS,CACd,gBAAgB,CAAC,8FAA8F,CAAC,EAChH,EAAE,CACH,CAAC;IACF,MAAM,CAAC,SAAS,CACd,gBAAgB,CAAC,gGAAgG,CAAC,EAClH,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,qBAAqB,EAAE,CAAC,CAC1E,CAAC;IAEF,MAAM,CAAC,SAAS,CACd,gBAAgB,CAAC,uFAAuF,CAAC,EACzG,EAAE,CACH,CAAC;IACF,MAAM,CAAC,SAAS,CACd,gBAAgB,CACd,kGAAkG,CACnG,EACD,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,CACjE,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,8EAA8E,EAAE,GAAG,EAAE;IACxF,MAAM,OAAO,GAAG,gBAAgB,CAC9B,8HAA8H,EAC9H,EAAE,GAAG,EAAE,OAAO,EAAE,CACjB,CAAC;IACF,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE;QACxB,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,yBAAyB,EAAE;KAC3F,CAAC,CAAC;IAEH,uFAAuF;IACvF,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,0EAA0E,CAAC,EAAE,EAAE,CAAC,CAAC;IACnH,MAAM,CAAC,SAAS,CACd,gBAAgB,CACd,sGAAsG,CACvG,EACD,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,sBAAsB,EAAE,CAAC,CAC1F,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,6DAA6D,EAAE,GAAG,EAAE;IACvE,kGAAkG;IAClG,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,4EAA4E,CAAC,EAAE,EAAE,CAAC,CAAC;IACrH,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,oCAAoC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC7E,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,iBAAiB,CAAC,EAAE,EAAE,CAAC,CAAC;IAC1D,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;AAChD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,0EAA0E,EAAE,GAAG,EAAE;IACpF,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,iBAAiB,EAAE,CAAC,EAAE,cAAc,CAAC,CAAC;IACvF,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,oBAAoB,EAAE,EAAE,OAAO,CAAC,EAAE,oBAAoB,CAAC,CAAC;IACtG,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,EAAE,qBAAqB,CAAC,CAAC;IAC1F,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,EAAE,eAAe,CAAC,CAAC;IAClF,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,QAAQ,CAAC,CAAC;AAChE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mEAAmE,EAAE,KAAK,IAAI,EAAE;IACnF,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,kBAAkB,CAAC,CAAC;IACtD,MAAM,OAAO,GAAG,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAE1C,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC1D,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC;IAClD,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,CAAC;IACpD,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;IAChD,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;IAChD,MAAM,CAAC,KAAK,CACV,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EAClE,KAAK,EACL,qDAAqD,CACtD,CAAC;IAEF,MAAM,CAAC,SAAS,CACd,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,EACnF,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAC1B,CAAC;IACF,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC/C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,2BAA2B,CAAC,CAAC;AACrE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,2EAA2E,EAAE,KAAK,IAAI,EAAE;IAC3F,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,kBAAkB,CAAC,CAAC;IACtD,MAAM,KAAK,GAAG,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAExC,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC;QACnC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,cAAc,IAAI,sBAAsB,CAAC,CAAC;IAC/G,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;IAClE,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,kBAAkB,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAE9C,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACpE,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AACpF,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,sDAAsD,EAAE,GAAG,EAAE;IAChE,MAAM,OAAO,GAAqB,EAAE,CAAC;IACrC,MAAM,MAAM,GAAG,uBAAuB,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;IAC1E,MAAM,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC;IAC7D,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAChC,MAAM,CAAC,KAAK,EAAE,CAAC;IACf,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AAClC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,gFAAgF,EAAE,KAAK,IAAI,EAAE;IAChG,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,kBAAkB,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,uBAAuB,EAAE,CAAC;IAC3C,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC;QAC5C,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IACD,QAAQ,CAAC,KAAK,EAAE,CAAC;IAEjB,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,YAAY,EAAE,EAAE,2BAA2B,CAAC,CAAC;IACnE,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,iBAAiB,EAAE,EAAE,IAAI,CAAC,CAAC;AACnD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,4DAA4D,EAAE,GAAG,EAAE;IACtE,MAAM,QAAQ,GAAG,uBAAuB,EAAE,CAAC;IAC3C,QAAQ,CAAC,IAAI,CACX;QACE,IAAI,CAAC,SAAS,CAAC;YACb,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE;gBACP,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,EAAE;gBACX,UAAU,EAAE,OAAO;gBACnB,WAAW,EAAE,GAAG;gBAChB,YAAY,EAAE,cAAc;aAC7B;SACF,CAAC;QACF,IAAI,CAAC,SAAS,CAAC;YACb,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE;gBACP,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC;gBACrD,UAAU,EAAE,MAAM;aACnB;SACF,CAAC;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;IACF,QAAQ,CAAC,KAAK,EAAE,CAAC;IAEjB,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,YAAY,EAAE,EAAE,kBAAkB,CAAC,CAAC;IAC1D,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,iBAAiB,EAAE,EAAE,IAAI,CAAC,CAAC;AACnD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,2EAA2E,EAAE,GAAG,EAAE;IACrF,MAAM,QAAQ,GAAG,uBAAuB,EAAE,CAAC;IAC3C,QAAQ,CAAC,IAAI,CACX,GAAG,IAAI,CAAC,SAAS,CAAC;QAChB,IAAI,EAAE,gBAAgB;QACtB,qBAAqB,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,2BAA2B,EAAE;KAClF,CAAC,IAAI,CACP,CAAC;IACF,QAAQ,CAAC,KAAK,EAAE,CAAC;IAEjB,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,YAAY,EAAE,EAAE,IAAI,CAAC,CAAC;IAC5C,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,uBAAuB,EAAE,EAAE,2BAA2B,CAAC,CAAC;AAChF,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qEAAqE,EAAE,KAAK,IAAI,EAAE;IACrF,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,kBAAkB,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,uBAAuB,EAAE,CAAC;IAC3C,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,CAAC;IAEjB,MAAM,SAAS,GAAG,QAAQ,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC;IAChD,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,mDAAmD,CAAC,CAAC;IAC5G,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,KAAK,EAAE,0CAA0C,CAAC,CAAC;AACzG,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,oFAAoF,EAAE,KAAK,IAAI,EAAE;IACpG,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,gBAAgB,CAAC,CAAC;IACpD,MAAM,QAAQ,GAAG,uBAAuB,EAAE,CAAC;IAC3C,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC;QAC5C,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IACD,QAAQ,CAAC,KAAK,EAAE,CAAC;IAEjB,MAAM,OAAO,GAAG,QAAQ,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC;IACnD,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC7B,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,sCAAsC,CAAC,CAAC;IAC9D,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,YAAY,EAAE,EAAE,IAAI,EAAE,sCAAsC,CAAC,CAAC;AACtF,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;IACxD,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,gBAAgB,CAAC,CAAC;IACpD,MAAM,QAAQ,GAAG,uBAAuB,EAAE,CAAC;IAC3C,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAChD,QAAQ,CAAC,KAAK,EAAE,CAAC;IAEjB,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,iBAAiB,EAAE,IAAI,EAAE,EAAE,gCAAgC,CAAC,CAAC;AACrF,CAAC,CAAC,CAAC"}
|
package/dist/runners/omp.d.ts
CHANGED
|
@@ -17,14 +17,16 @@ import type { Runner, RunnerOptions, RunResult } from './types.js';
|
|
|
17
17
|
* measured surface. Default config is already `tools.approvalMode=yolo`.
|
|
18
18
|
* - `--cwd` sets the workspace. `--model` is consumed; unknown ids exit 1 with
|
|
19
19
|
* `Model "…" not found` (no silent fallback). `default` is the platform sentinel.
|
|
20
|
-
* -
|
|
21
|
-
*
|
|
22
|
-
* stderr
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* -
|
|
26
|
-
*
|
|
27
|
-
*
|
|
20
|
+
* - `--mode json`은 필수다(2026-08-26 v18.0.5 실측, 2026-08-29 omp/18.0.6 재확인).
|
|
21
|
+
* 기본 text 모드는 실행 내내 stdout이 0바이트이고 최종 답변만 종료 시점에 한 번에
|
|
22
|
+
* 나온다. stderr도 `Working...` 한 줄뿐이라 idle 타이머를 리셋할 계기가 없어,
|
|
23
|
+
* 10분 넘는 정상 실행이 `Runner idle timed out after 10m of no output`으로 죽었다.
|
|
24
|
+
* json 모드는 같은 작업에서 NDJSON을 실행 내내 연속 방출한다.
|
|
25
|
+
* - json 모드를 한 번 기각했던 근거(공급자 실패에도 **exit 0**)는 사실이지만, 종료
|
|
26
|
+
* 코드 대신 스트림으로 판정하면 해소된다. 잘못된 API 키로 재현하면 exit 0이면서도
|
|
27
|
+
* `message_start`/`message_end`/`turn_end` 세 이벤트가 모두 `stopReason:"error"`와
|
|
28
|
+
* `errorStatus`/`errorMessage`를 싣는다. 그래서 실패 판정 축은 종료 코드가 아니라
|
|
29
|
+
* `stopReason`이다 — 캡처된 사유가 있으면 exit 0이어도 실패로 보고한다.
|
|
28
30
|
*/
|
|
29
31
|
export declare const buildOmpArgs: (promptFilePath: string, cwd: string, model?: string | null) => string[];
|
|
30
32
|
export declare const getOmpExecutablePreference: (isWindows: boolean) => string[];
|
package/dist/runners/omp.js
CHANGED
|
@@ -6,12 +6,12 @@ import { dirname, join } from 'node:path';
|
|
|
6
6
|
import { resolveExecutablePathsWithPreferenceAsync, runProbeCommand } from '../executable.js';
|
|
7
7
|
import { logger } from '../logger.js';
|
|
8
8
|
import { selectRunnerFailureMessage } from './failure-message.js';
|
|
9
|
-
import { createAnsiStripper
|
|
9
|
+
import { createAnsiStripper } from './kiro-cli.js';
|
|
10
10
|
import { findOmpExecutable, isOmpExecutable } from './omp-identity.js';
|
|
11
|
+
import { createOmpStreamConsumer } from './omp-json-parser.js';
|
|
11
12
|
import { setupCloseWatchdog, terminateRunnerChild } from './process-control.js';
|
|
12
13
|
import { buildRunnerChildEnv } from './session-env.js';
|
|
13
14
|
const OUTPUT_PREVIEW_MAX = 400;
|
|
14
|
-
const OUTPUT_CAPTURE_MAX = 200_000;
|
|
15
15
|
const normalizedModel = (model) => (typeof model === 'string' ? model.trim() : '');
|
|
16
16
|
const toPromptAttachArg = (promptFilePath) => `@${promptFilePath}`;
|
|
17
17
|
/**
|
|
@@ -26,20 +26,24 @@ const toPromptAttachArg = (promptFilePath) => `@${promptFilePath}`;
|
|
|
26
26
|
* measured surface. Default config is already `tools.approvalMode=yolo`.
|
|
27
27
|
* - `--cwd` sets the workspace. `--model` is consumed; unknown ids exit 1 with
|
|
28
28
|
* `Model "…" not found` (no silent fallback). `default` is the platform sentinel.
|
|
29
|
-
* -
|
|
30
|
-
*
|
|
31
|
-
* stderr
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* -
|
|
35
|
-
*
|
|
36
|
-
*
|
|
29
|
+
* - `--mode json`은 필수다(2026-08-26 v18.0.5 실측, 2026-08-29 omp/18.0.6 재확인).
|
|
30
|
+
* 기본 text 모드는 실행 내내 stdout이 0바이트이고 최종 답변만 종료 시점에 한 번에
|
|
31
|
+
* 나온다. stderr도 `Working...` 한 줄뿐이라 idle 타이머를 리셋할 계기가 없어,
|
|
32
|
+
* 10분 넘는 정상 실행이 `Runner idle timed out after 10m of no output`으로 죽었다.
|
|
33
|
+
* json 모드는 같은 작업에서 NDJSON을 실행 내내 연속 방출한다.
|
|
34
|
+
* - json 모드를 한 번 기각했던 근거(공급자 실패에도 **exit 0**)는 사실이지만, 종료
|
|
35
|
+
* 코드 대신 스트림으로 판정하면 해소된다. 잘못된 API 키로 재현하면 exit 0이면서도
|
|
36
|
+
* `message_start`/`message_end`/`turn_end` 세 이벤트가 모두 `stopReason:"error"`와
|
|
37
|
+
* `errorStatus`/`errorMessage`를 싣는다. 그래서 실패 판정 축은 종료 코드가 아니라
|
|
38
|
+
* `stopReason`이다 — 캡처된 사유가 있으면 exit 0이어도 실패로 보고한다.
|
|
37
39
|
*/
|
|
38
40
|
export const buildOmpArgs = (promptFilePath, cwd, model) => {
|
|
39
41
|
const selectedModel = normalizedModel(model);
|
|
40
42
|
const modelArgs = selectedModel.length > 0 && selectedModel !== 'default' ? ['--model', selectedModel] : [];
|
|
41
43
|
return [
|
|
42
44
|
'-p',
|
|
45
|
+
'--mode',
|
|
46
|
+
'json',
|
|
43
47
|
'--no-session',
|
|
44
48
|
'--auto-approve',
|
|
45
49
|
'--approval-mode',
|
|
@@ -76,7 +80,8 @@ const toOutputPreview = (text) => {
|
|
|
76
80
|
* **차단목록**이다 — 오류 문구를 열거하면 형태를 모르는 공급자 오류를 통째로 놓친다.
|
|
77
81
|
*
|
|
78
82
|
* 여기 열거한 진행 문구는 omp/18.0.4 바이너리에서 실제로 stderr에 쓰는 것들이다
|
|
79
|
-
* (2026-08-25 실측).
|
|
83
|
+
* (2026-08-25 실측). `--mode json`으로 전환한 뒤에는 `Working...`이 나오지 않지만
|
|
84
|
+
* (2026-08-26 실측), 나머지 기동 안내는 모드와 무관하므로 차단목록을 유지한다.
|
|
80
85
|
* - `Working...`: 요청 시작 시점에 text 모드에서 한 번 쓴다. **성공 실행에서도 나온다.**
|
|
81
86
|
* 이 줄을 걸러내지 않으면 첫 오류 신호로 latch돼 수 초 뒤 도착하는 진짜 사유
|
|
82
87
|
* (`402 Insufficient credits …`, `404 No endpoints found …`)를 덮어쓴다.
|
|
@@ -210,26 +215,27 @@ export class OmpRunner {
|
|
|
210
215
|
let lastOutput = '';
|
|
211
216
|
let lastErrorOutput = '';
|
|
212
217
|
let firstErrorSignal = '';
|
|
213
|
-
let outputText = '';
|
|
214
|
-
const appendOutputText = (chunk) => {
|
|
215
|
-
if (outputText.length < OUTPUT_CAPTURE_MAX) {
|
|
216
|
-
outputText += chunk.slice(0, OUTPUT_CAPTURE_MAX - outputText.length);
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
218
|
const stdoutStripper = createAnsiStripper();
|
|
220
219
|
const stderrStripper = createAnsiStripper();
|
|
221
220
|
const idleTimer = { reset: () => { } };
|
|
221
|
+
// 각 NDJSON 라인을 한 번만 파싱해 라이브 로그와 결과 판정 양쪽에 전달한다.
|
|
222
|
+
const streamConsumer = createOmpStreamConsumer((entries) => {
|
|
223
|
+
for (const entry of entries) {
|
|
224
|
+
lastOutput = entry.message;
|
|
225
|
+
opts.onStdoutChunk?.(entry.message, entry.category, entry.toolName);
|
|
226
|
+
}
|
|
227
|
+
}, { cwd });
|
|
222
228
|
child.stdout?.on('data', (chunk) => {
|
|
223
229
|
const rawOutput = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk);
|
|
224
230
|
const text = stdoutStripper.push(rawOutput);
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if (output.length > 0) {
|
|
228
|
-
lastOutput = output;
|
|
229
|
-
idleTimer.reset();
|
|
230
|
-
opts.onStdoutChunk?.(output, 'TEXT');
|
|
231
|
-
logger.info('Runner stdout', { triggerId: opts.triggerId, pid: child.pid, output });
|
|
231
|
+
if (text.length === 0) {
|
|
232
|
+
return;
|
|
232
233
|
}
|
|
234
|
+
streamConsumer.push(text);
|
|
235
|
+
// 이벤트가 로그 엔트리로 정제되지 않는 구간(델타 연발)에도 살아 있다는 신호이므로,
|
|
236
|
+
// 리셋은 정제 결과가 아니라 데이터 도착을 기준으로 한다.
|
|
237
|
+
idleTimer.reset();
|
|
238
|
+
logger.info('Runner stdout', { triggerId: opts.triggerId, pid: child.pid, output: toOutputPreview(text) });
|
|
233
239
|
});
|
|
234
240
|
child.stderr?.on('data', (chunk) => {
|
|
235
241
|
const rawOutput = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk);
|
|
@@ -240,6 +246,7 @@ export class OmpRunner {
|
|
|
240
246
|
firstErrorSignal = output;
|
|
241
247
|
}
|
|
242
248
|
idleTimer.reset();
|
|
249
|
+
opts.onStderrChunk?.(output, 'STDERR');
|
|
243
250
|
logger.info('Oh My Pi progress', { triggerId: opts.triggerId, pid: child.pid, output });
|
|
244
251
|
}
|
|
245
252
|
});
|
|
@@ -292,7 +299,12 @@ export class OmpRunner {
|
|
|
292
299
|
clearTimeout(timeoutId);
|
|
293
300
|
await cleanup();
|
|
294
301
|
logger.error('Runner process launch failed', { triggerId: opts.triggerId, error: error.message });
|
|
295
|
-
resolve({
|
|
302
|
+
resolve({
|
|
303
|
+
exitCode: 1,
|
|
304
|
+
lastOutput,
|
|
305
|
+
outputText: streamConsumer.getFinalText() ?? streamConsumer.getStreamedTextFallback() ?? undefined,
|
|
306
|
+
errorMessage: error.message,
|
|
307
|
+
});
|
|
296
308
|
});
|
|
297
309
|
const closeWatchdog = this.deps.setupCloseWatchdog(child, opts.triggerId);
|
|
298
310
|
child.on('close', async (code) => {
|
|
@@ -300,8 +312,11 @@ export class OmpRunner {
|
|
|
300
312
|
clearTimeout(timeoutId);
|
|
301
313
|
await cleanup();
|
|
302
314
|
logger.info('Runner process closed', { triggerId: opts.triggerId, pid: child.pid, exitCode: code, timedOut });
|
|
303
|
-
|
|
304
|
-
|
|
315
|
+
stdoutStripper.flush();
|
|
316
|
+
// 라인 스캐너가 개행 없이 끝난 마지막 NDJSON 라인을 회수한다.
|
|
317
|
+
streamConsumer.flush();
|
|
318
|
+
const finalizedOutputText = streamConsumer.getFinalText() ?? streamConsumer.getStreamedTextFallback() ?? undefined;
|
|
319
|
+
const streamFailureMessage = streamConsumer.getFailureMessage();
|
|
305
320
|
if (timedOut) {
|
|
306
321
|
resolve({
|
|
307
322
|
exitCode: 1,
|
|
@@ -324,6 +339,17 @@ export class OmpRunner {
|
|
|
324
339
|
});
|
|
325
340
|
return;
|
|
326
341
|
}
|
|
342
|
+
// json 모드는 공급자 실패에도 exit 0을 돌려준다. 스트림이 실패를 명시했다면 종료
|
|
343
|
+
// 코드가 무엇이든 실패로 보고한다.
|
|
344
|
+
if (streamFailureMessage) {
|
|
345
|
+
resolve({
|
|
346
|
+
exitCode: code && code !== 0 ? code : 1,
|
|
347
|
+
lastOutput,
|
|
348
|
+
outputText: finalizedOutputText,
|
|
349
|
+
errorMessage: streamFailureMessage,
|
|
350
|
+
});
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
327
353
|
resolve({
|
|
328
354
|
exitCode: code ?? 1,
|
|
329
355
|
lastOutput,
|