ai-cli-mcp 2.19.0 → 2.20.1
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/CHANGELOG.md +26 -0
- package/README.ja.md +34 -8
- package/README.md +41 -8
- package/dist/app/cli.js +1 -0
- package/dist/app/mcp.js +64 -12
- package/dist/cli-builder.js +13 -6
- package/dist/cli-process-service.js +76 -91
- package/dist/cli-utils.js +6 -0
- package/dist/cli.js +1 -1
- package/dist/model-catalog.js +3 -2
- package/dist/parsers.js +8 -2
- package/package.json +27 -3
- package/server.json +3 -3
- package/.gemini/settings.json +0 -11
- package/.github/dependabot.yml +0 -28
- package/.github/pull_request_template.md +0 -28
- package/.github/workflows/ci.yml +0 -34
- package/.github/workflows/dependency-review.yml +0 -22
- package/.github/workflows/publish.yml +0 -89
- package/.github/workflows/test.yml +0 -20
- package/.github/workflows/watch-session-prs.yml +0 -276
- package/.husky/pre-commit +0 -1
- package/.mcp.json +0 -11
- package/.releaserc.json +0 -18
- package/.vscode/settings.json +0 -3
- package/CONTRIBUTING.md +0 -81
- package/dist/__tests__/app-cli.test.js +0 -392
- package/dist/__tests__/cli-bin-smoke.test.js +0 -101
- package/dist/__tests__/cli-builder.test.js +0 -442
- package/dist/__tests__/cli-process-service.test.js +0 -655
- package/dist/__tests__/cli-utils.test.js +0 -171
- package/dist/__tests__/e2e.test.js +0 -256
- package/dist/__tests__/edge-cases.test.js +0 -130
- package/dist/__tests__/error-cases.test.js +0 -292
- package/dist/__tests__/mcp-contract.test.js +0 -636
- package/dist/__tests__/mocks.js +0 -32
- package/dist/__tests__/model-alias.test.js +0 -36
- package/dist/__tests__/parsers.test.js +0 -646
- package/dist/__tests__/peek.test.js +0 -36
- package/dist/__tests__/process-management.test.js +0 -949
- package/dist/__tests__/server.test.js +0 -809
- package/dist/__tests__/setup.js +0 -11
- package/dist/__tests__/utils/claude-mock.js +0 -80
- package/dist/__tests__/utils/mcp-client.js +0 -121
- package/dist/__tests__/utils/opencode-mock.js +0 -91
- package/dist/__tests__/utils/persistent-mock.js +0 -28
- package/dist/__tests__/utils/test-helpers.js +0 -11
- package/dist/__tests__/validation.test.js +0 -308
- package/dist/__tests__/version-print.test.js +0 -65
- package/dist/__tests__/wait.test.js +0 -260
- package/docs/RELEASE_CHECKLIST.md +0 -65
- package/docs/cli-architecture.md +0 -275
- package/docs/concept.md +0 -154
- package/docs/development.md +0 -156
- package/docs/e2e-testing.md +0 -148
- package/docs/prd.md +0 -146
- package/docs/session-stacking.md +0 -67
- package/src/__tests__/app-cli.test.ts +0 -495
- package/src/__tests__/cli-bin-smoke.test.ts +0 -136
- package/src/__tests__/cli-builder.test.ts +0 -549
- package/src/__tests__/cli-process-service.test.ts +0 -759
- package/src/__tests__/cli-utils.test.ts +0 -200
- package/src/__tests__/e2e.test.ts +0 -311
- package/src/__tests__/edge-cases.test.ts +0 -176
- package/src/__tests__/error-cases.test.ts +0 -370
- package/src/__tests__/mcp-contract.test.ts +0 -755
- package/src/__tests__/mocks.ts +0 -35
- package/src/__tests__/model-alias.test.ts +0 -44
- package/src/__tests__/parsers.test.ts +0 -730
- package/src/__tests__/peek.test.ts +0 -44
- package/src/__tests__/process-management.test.ts +0 -1129
- package/src/__tests__/server.test.ts +0 -1020
- package/src/__tests__/setup.ts +0 -13
- package/src/__tests__/utils/claude-mock.ts +0 -87
- package/src/__tests__/utils/mcp-client.ts +0 -159
- package/src/__tests__/utils/opencode-mock.ts +0 -108
- package/src/__tests__/utils/persistent-mock.ts +0 -33
- package/src/__tests__/utils/test-helpers.ts +0 -13
- package/src/__tests__/validation.test.ts +0 -369
- package/src/__tests__/version-print.test.ts +0 -81
- package/src/__tests__/wait.test.ts +0 -302
- package/src/app/cli.ts +0 -424
- package/src/app/mcp.ts +0 -466
- package/src/bin/ai-cli-mcp.ts +0 -7
- package/src/bin/ai-cli.ts +0 -11
- package/src/cli-builder.ts +0 -274
- package/src/cli-parse.ts +0 -105
- package/src/cli-process-service.ts +0 -709
- package/src/cli-utils.ts +0 -258
- package/src/cli.ts +0 -124
- package/src/model-catalog.ts +0 -87
- package/src/parsers.ts +0 -965
- package/src/peek.ts +0 -95
- package/src/process-result.ts +0 -88
- package/src/process-service.ts +0 -368
- package/src/server.ts +0 -10
- package/tsconfig.json +0 -16
- package/vitest.config.e2e.ts +0 -27
- package/vitest.config.ts +0 -22
- package/vitest.config.unit.ts +0 -28
package/src/parsers.ts
DELETED
|
@@ -1,965 +0,0 @@
|
|
|
1
|
-
import { debugLog } from './cli-utils.js';
|
|
2
|
-
|
|
3
|
-
export interface PeekMessage {
|
|
4
|
-
ts: string;
|
|
5
|
-
text: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export type PeekToolCallStatus = 'success' | 'failed' | 'cancelled' | 'unknown';
|
|
9
|
-
|
|
10
|
-
export type PeekEvent =
|
|
11
|
-
| { kind: 'message'; ts: string; text: string }
|
|
12
|
-
| {
|
|
13
|
-
kind: 'tool_call';
|
|
14
|
-
ts: string;
|
|
15
|
-
phase: 'started' | 'completed';
|
|
16
|
-
tool: string;
|
|
17
|
-
summary: string;
|
|
18
|
-
id?: string;
|
|
19
|
-
status?: PeekToolCallStatus;
|
|
20
|
-
server?: string;
|
|
21
|
-
exit_code?: number;
|
|
22
|
-
duration_ms?: number;
|
|
23
|
-
summary_truncated?: boolean;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
type PeekToolCallEvent = Extract<PeekEvent, { kind: 'tool_call' }>;
|
|
27
|
-
|
|
28
|
-
type PeekAgent = 'claude' | 'codex' | string | null;
|
|
29
|
-
|
|
30
|
-
interface PeekEventExtractorOptions {
|
|
31
|
-
includeToolCalls?: boolean;
|
|
32
|
-
source?: 'stdout' | 'stderr';
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
interface PeekFlushOptions {
|
|
36
|
-
terminal?: boolean;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface ToolSummary {
|
|
40
|
-
summary: string;
|
|
41
|
-
server?: string;
|
|
42
|
-
summary_truncated?: boolean;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface ToolCallMemory {
|
|
46
|
-
tool: string;
|
|
47
|
-
server?: string;
|
|
48
|
-
summary: string;
|
|
49
|
-
summary_truncated?: boolean;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
interface PendingForgeTool {
|
|
53
|
-
id: string;
|
|
54
|
-
tool: string;
|
|
55
|
-
summary: string;
|
|
56
|
-
summary_truncated?: boolean;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const PEEK_TOOL_SUMMARY_MAX_LENGTH = 200;
|
|
60
|
-
const FORGE_EXECUTE_PATTERN = /^● \[[^\]]+\] Execute \[([^\]]*)\]\s+(.+)$/;
|
|
61
|
-
const FORGE_FINISHED_PATTERN = /^● \[[^\]]+\] Finished(?:\s+\S+)?\s*$/;
|
|
62
|
-
|
|
63
|
-
function isGeminiAssistantMessageEvent(parsed: any): boolean {
|
|
64
|
-
return parsed.type === 'message' && parsed.role === 'assistant' && typeof parsed.content === 'string';
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const GEMINI_STREAM_EVENT_TYPES = new Set([
|
|
68
|
-
'init',
|
|
69
|
-
'message',
|
|
70
|
-
'tool_use',
|
|
71
|
-
'tool_result',
|
|
72
|
-
'result',
|
|
73
|
-
'error',
|
|
74
|
-
'stats',
|
|
75
|
-
]);
|
|
76
|
-
|
|
77
|
-
function isGeminiStreamJsonEvent(parsed: any): boolean {
|
|
78
|
-
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) && GEMINI_STREAM_EVENT_TYPES.has(parsed.type);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function oneLine(value: unknown): string {
|
|
82
|
-
return String(value ?? '').replace(/\s+/g, ' ').trim();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function boundedSummary(value: string): { summary: string; summary_truncated?: boolean } {
|
|
86
|
-
const summary = oneLine(value);
|
|
87
|
-
if (summary.length <= PEEK_TOOL_SUMMARY_MAX_LENGTH) {
|
|
88
|
-
return { summary };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
summary: `${summary.slice(0, PEEK_TOOL_SUMMARY_MAX_LENGTH - 3)}...`,
|
|
93
|
-
summary_truncated: true,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function normalizeMcpToolName(tool: string, explicitServer?: string): ToolSummary | null {
|
|
98
|
-
if (explicitServer) {
|
|
99
|
-
return {
|
|
100
|
-
server: explicitServer,
|
|
101
|
-
...boundedSummary(`${explicitServer}.${tool}`),
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const mcpDouble = tool.match(/^mcp__([^_]+)__(.+)$/);
|
|
106
|
-
if (mcpDouble) {
|
|
107
|
-
return {
|
|
108
|
-
server: mcpDouble[1],
|
|
109
|
-
...boundedSummary(`${mcpDouble[1]}.${mcpDouble[2]}`),
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const mcpSingle = tool.match(/^mcp_([^_]+)_(.+)$/);
|
|
114
|
-
if (mcpSingle) {
|
|
115
|
-
return {
|
|
116
|
-
server: mcpSingle[1],
|
|
117
|
-
...boundedSummary(`${mcpSingle[1]}.${mcpSingle[2]}`),
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const acmShort = tool.match(/^acm_(.+)$/);
|
|
122
|
-
if (acmShort) {
|
|
123
|
-
return {
|
|
124
|
-
server: 'acm',
|
|
125
|
-
...boundedSummary(`acm.${acmShort[1]}`),
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function buildToolSummary(tool: string, options: { server?: string; command?: unknown } = {}): ToolSummary {
|
|
133
|
-
if (typeof options.command === 'string' && options.command.trim()) {
|
|
134
|
-
return boundedSummary(options.command);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const mcpSummary = normalizeMcpToolName(tool, options.server);
|
|
138
|
-
if (mcpSummary) {
|
|
139
|
-
return mcpSummary;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return boundedSummary(tool || 'tool_call');
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function normalizeToolStatus(rawStatus: unknown, exitCode?: number, defaultStatus: PeekToolCallStatus = 'unknown'): PeekToolCallStatus {
|
|
146
|
-
if (typeof exitCode === 'number') {
|
|
147
|
-
return exitCode === 0 ? 'success' : 'failed';
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const status = typeof rawStatus === 'string' ? rawStatus.toLowerCase() : '';
|
|
151
|
-
if (['success', 'succeeded', 'ok', 'completed'].includes(status)) {
|
|
152
|
-
return 'success';
|
|
153
|
-
}
|
|
154
|
-
if (['failed', 'failure', 'error', 'errored'].includes(status)) {
|
|
155
|
-
return 'failed';
|
|
156
|
-
}
|
|
157
|
-
if (['cancelled', 'canceled'].includes(status)) {
|
|
158
|
-
return 'cancelled';
|
|
159
|
-
}
|
|
160
|
-
return defaultStatus;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function createToolCallEvent(params: {
|
|
164
|
-
ts: string;
|
|
165
|
-
phase: 'started' | 'completed';
|
|
166
|
-
tool: string;
|
|
167
|
-
id?: string;
|
|
168
|
-
server?: string;
|
|
169
|
-
command?: unknown;
|
|
170
|
-
status?: unknown;
|
|
171
|
-
defaultStatus?: PeekToolCallStatus;
|
|
172
|
-
exit_code?: number;
|
|
173
|
-
duration_ms?: number;
|
|
174
|
-
}): PeekToolCallEvent {
|
|
175
|
-
const tool = params.tool || 'tool_call';
|
|
176
|
-
const summary = buildToolSummary(tool, { server: params.server, command: params.command });
|
|
177
|
-
const event: PeekToolCallEvent = {
|
|
178
|
-
kind: 'tool_call',
|
|
179
|
-
ts: params.ts,
|
|
180
|
-
phase: params.phase,
|
|
181
|
-
tool,
|
|
182
|
-
summary: summary.summary,
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
if (params.id) {
|
|
186
|
-
event.id = params.id;
|
|
187
|
-
}
|
|
188
|
-
if (summary.server) {
|
|
189
|
-
event.server = summary.server;
|
|
190
|
-
} else if (params.server) {
|
|
191
|
-
event.server = params.server;
|
|
192
|
-
}
|
|
193
|
-
if (summary.summary_truncated) {
|
|
194
|
-
event.summary_truncated = true;
|
|
195
|
-
}
|
|
196
|
-
if (params.phase === 'completed') {
|
|
197
|
-
event.status = normalizeToolStatus(params.status, params.exit_code, params.defaultStatus);
|
|
198
|
-
if (typeof params.exit_code === 'number') {
|
|
199
|
-
event.exit_code = params.exit_code;
|
|
200
|
-
}
|
|
201
|
-
if (typeof params.duration_ms === 'number' && Number.isFinite(params.duration_ms)) {
|
|
202
|
-
event.duration_ms = params.duration_ms;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return event;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function rememberToolCall(event: PeekEvent, memory: Map<string, ToolCallMemory>): void {
|
|
210
|
-
if (event.kind !== 'tool_call' || !event.id) {
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
memory.set(event.id, {
|
|
215
|
-
tool: event.tool,
|
|
216
|
-
server: event.server,
|
|
217
|
-
summary: event.summary,
|
|
218
|
-
summary_truncated: event.summary_truncated,
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function createRememberedCompletion(params: {
|
|
223
|
-
ts: string;
|
|
224
|
-
id?: string;
|
|
225
|
-
memory: Map<string, ToolCallMemory>;
|
|
226
|
-
fallbackTool: string;
|
|
227
|
-
status?: unknown;
|
|
228
|
-
defaultStatus?: PeekToolCallStatus;
|
|
229
|
-
}): PeekEvent {
|
|
230
|
-
const remembered = params.id ? params.memory.get(params.id) : undefined;
|
|
231
|
-
const event = createToolCallEvent({
|
|
232
|
-
ts: params.ts,
|
|
233
|
-
phase: 'completed',
|
|
234
|
-
id: params.id,
|
|
235
|
-
tool: remembered?.tool || params.fallbackTool,
|
|
236
|
-
server: remembered?.server,
|
|
237
|
-
status: params.status,
|
|
238
|
-
defaultStatus: params.defaultStatus,
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
if (remembered) {
|
|
242
|
-
event.summary = remembered.summary;
|
|
243
|
-
if (remembered.summary_truncated) {
|
|
244
|
-
event.summary_truncated = true;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
return event;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function extractPeekEventsFromParsedEvent(agent: PeekAgent, parsed: any, observedAt: string, includeToolCalls: boolean, memory: Map<string, ToolCallMemory>): PeekEvent[] {
|
|
252
|
-
if (agent === 'codex') {
|
|
253
|
-
if (parsed.item?.type === 'agent_message' && typeof parsed.item.text === 'string' && parsed.item.text.trim()) {
|
|
254
|
-
return [{ kind: 'message', ts: observedAt, text: parsed.item.text }];
|
|
255
|
-
}
|
|
256
|
-
if (parsed.msg?.type === 'agent_message' && typeof parsed.msg.message === 'string' && parsed.msg.message.trim()) {
|
|
257
|
-
return [{ kind: 'message', ts: observedAt, text: parsed.msg.message }];
|
|
258
|
-
}
|
|
259
|
-
if (includeToolCalls && (parsed.type === 'item.started' || parsed.type === 'item.completed')) {
|
|
260
|
-
const item = parsed.item;
|
|
261
|
-
if (item?.type === 'command_execution') {
|
|
262
|
-
const event = createToolCallEvent({
|
|
263
|
-
ts: observedAt,
|
|
264
|
-
phase: parsed.type === 'item.started' ? 'started' : 'completed',
|
|
265
|
-
id: item.id,
|
|
266
|
-
tool: 'command_execution',
|
|
267
|
-
command: item.command,
|
|
268
|
-
status: item.status || item.error,
|
|
269
|
-
exit_code: typeof item.exit_code === 'number' ? item.exit_code : undefined,
|
|
270
|
-
defaultStatus: parsed.type === 'item.completed' ? 'success' : 'unknown',
|
|
271
|
-
});
|
|
272
|
-
rememberToolCall(event, memory);
|
|
273
|
-
return [event];
|
|
274
|
-
}
|
|
275
|
-
if (item?.type === 'mcp_tool_call') {
|
|
276
|
-
const event = createToolCallEvent({
|
|
277
|
-
ts: observedAt,
|
|
278
|
-
phase: parsed.type === 'item.started' ? 'started' : 'completed',
|
|
279
|
-
id: item.id,
|
|
280
|
-
tool: item.tool || 'mcp_tool_call',
|
|
281
|
-
server: item.server,
|
|
282
|
-
status: item.status || item.error,
|
|
283
|
-
defaultStatus: parsed.type === 'item.completed' ? 'success' : 'unknown',
|
|
284
|
-
});
|
|
285
|
-
rememberToolCall(event, memory);
|
|
286
|
-
return [event];
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
return [];
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (agent === 'claude') {
|
|
293
|
-
if (parsed.type === 'assistant' && Array.isArray(parsed.message?.content)) {
|
|
294
|
-
const events: PeekEvent[] = [];
|
|
295
|
-
for (const content of parsed.message.content) {
|
|
296
|
-
if (content?.type === 'text' && typeof content.text === 'string' && content.text.trim()) {
|
|
297
|
-
events.push({ kind: 'message', ts: observedAt, text: content.text });
|
|
298
|
-
} else if (includeToolCalls && content?.type === 'tool_use') {
|
|
299
|
-
const event = createToolCallEvent({
|
|
300
|
-
ts: observedAt,
|
|
301
|
-
phase: 'started',
|
|
302
|
-
id: content.id,
|
|
303
|
-
tool: content.name || 'tool_use',
|
|
304
|
-
command: content.input?.command,
|
|
305
|
-
});
|
|
306
|
-
rememberToolCall(event, memory);
|
|
307
|
-
events.push(event);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
return events;
|
|
311
|
-
}
|
|
312
|
-
if (includeToolCalls && parsed.type === 'user' && Array.isArray(parsed.message?.content)) {
|
|
313
|
-
const events: PeekEvent[] = [];
|
|
314
|
-
for (const content of parsed.message.content) {
|
|
315
|
-
if (content?.type === 'tool_result') {
|
|
316
|
-
events.push(createRememberedCompletion({
|
|
317
|
-
ts: observedAt,
|
|
318
|
-
id: content.tool_use_id,
|
|
319
|
-
memory,
|
|
320
|
-
fallbackTool: 'tool_result',
|
|
321
|
-
status: content.is_error === true ? 'failed' : undefined,
|
|
322
|
-
defaultStatus: content.is_error === true ? 'failed' : 'success',
|
|
323
|
-
}));
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
return events;
|
|
327
|
-
}
|
|
328
|
-
return [];
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (agent === 'opencode' && parsed.type === 'text' && parsed.part?.type === 'text' && typeof parsed.part.text === 'string' && parsed.part.text.trim()) {
|
|
332
|
-
return [{ kind: 'message', ts: observedAt, text: parsed.part.text }];
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
if (agent === 'opencode' && includeToolCalls && parsed.type === 'tool_use' && parsed.part?.type === 'tool') {
|
|
336
|
-
const state = parsed.part.state || {};
|
|
337
|
-
const start = state.time?.start;
|
|
338
|
-
const end = state.time?.end;
|
|
339
|
-
const event = createToolCallEvent({
|
|
340
|
-
ts: observedAt,
|
|
341
|
-
phase: state.status === 'running' || state.status === 'pending' ? 'started' : 'completed',
|
|
342
|
-
id: parsed.part.callID,
|
|
343
|
-
tool: parsed.part.tool || 'tool_use',
|
|
344
|
-
command: state.input?.command,
|
|
345
|
-
status: state.status,
|
|
346
|
-
defaultStatus: state.status === 'completed' ? 'success' : 'unknown',
|
|
347
|
-
duration_ms: typeof start === 'number' && typeof end === 'number' ? end - start : undefined,
|
|
348
|
-
});
|
|
349
|
-
rememberToolCall(event, memory);
|
|
350
|
-
return [event];
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
return [];
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
export class PeekEventExtractor {
|
|
357
|
-
private pending = '';
|
|
358
|
-
private geminiAssistantBuffer = '';
|
|
359
|
-
private readonly includeToolCalls: boolean;
|
|
360
|
-
private readonly source: 'stdout' | 'stderr';
|
|
361
|
-
private readonly toolMemory = new Map<string, ToolCallMemory>();
|
|
362
|
-
private forgePendingTool: PendingForgeTool | null = null;
|
|
363
|
-
private forgeToolSequence = 0;
|
|
364
|
-
|
|
365
|
-
constructor(private readonly agent: PeekAgent, options: PeekEventExtractorOptions = {}) {
|
|
366
|
-
this.includeToolCalls = options.includeToolCalls === true;
|
|
367
|
-
this.source = options.source || 'stdout';
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
push(chunk: string, observedAt = new Date().toISOString()): PeekEvent[] {
|
|
371
|
-
if (this.agent === 'forge' && this.source === 'stderr') {
|
|
372
|
-
return [];
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
if (!chunk) {
|
|
376
|
-
return [];
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
const lines = `${this.pending}${chunk}`.split(/\r?\n/);
|
|
380
|
-
this.pending = lines.pop() || '';
|
|
381
|
-
return this.extractLines(lines, observedAt);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
flush(observedAt = new Date().toISOString(), options: PeekFlushOptions = {}): PeekEvent[] {
|
|
385
|
-
if (this.agent === 'forge' && this.source === 'stderr') {
|
|
386
|
-
this.pending = '';
|
|
387
|
-
return [];
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const events: PeekEvent[] = [];
|
|
391
|
-
|
|
392
|
-
if (this.pending) {
|
|
393
|
-
if (this.agent !== 'forge' || options.terminal === true) {
|
|
394
|
-
const line = this.pending;
|
|
395
|
-
this.pending = '';
|
|
396
|
-
events.push(...this.extractLines([line], observedAt));
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
events.push(...this.flushGeminiAssistantBuffer(observedAt));
|
|
401
|
-
events.push(...this.flushForgePendingTool(observedAt, options.terminal === true));
|
|
402
|
-
return events;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
private extractLines(lines: string[], observedAt: string): PeekEvent[] {
|
|
406
|
-
if (this.agent === 'forge') {
|
|
407
|
-
return this.extractForgeLines(lines, observedAt);
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
const events: PeekEvent[] = [];
|
|
411
|
-
|
|
412
|
-
for (const line of lines) {
|
|
413
|
-
if (!line.trim()) {
|
|
414
|
-
continue;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
try {
|
|
418
|
-
events.push(...this.extractParsedEvent(JSON.parse(line), observedAt));
|
|
419
|
-
} catch {
|
|
420
|
-
debugLog(`[Debug] Skipping invalid peek JSON line: ${line}`);
|
|
421
|
-
events.push(...this.flushGeminiAssistantBuffer(observedAt));
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
return events;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
private extractForgeLines(lines: string[], observedAt: string): PeekEvent[] {
|
|
429
|
-
const events: PeekEvent[] = [];
|
|
430
|
-
|
|
431
|
-
for (const line of lines) {
|
|
432
|
-
if (!line.trim()) {
|
|
433
|
-
continue;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
const summary = this.extractForgeMessage(line, 'Summary:');
|
|
437
|
-
if (summary !== null) {
|
|
438
|
-
events.push({ kind: 'message', ts: observedAt, text: summary });
|
|
439
|
-
continue;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
const completed = this.extractForgeMessage(line, 'Completed successfully:');
|
|
443
|
-
if (completed !== null) {
|
|
444
|
-
events.push({ kind: 'message', ts: observedAt, text: completed });
|
|
445
|
-
continue;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
if (this.includeToolCalls) {
|
|
449
|
-
const executeMatch = line.match(FORGE_EXECUTE_PATTERN);
|
|
450
|
-
if (executeMatch) {
|
|
451
|
-
events.push(...this.completeForgePendingTool(observedAt));
|
|
452
|
-
const [, rawTool, rawSummary] = executeMatch;
|
|
453
|
-
const tool = rawTool.trim() && !/\s/.test(rawTool.trim()) ? rawTool.trim() : 'shell';
|
|
454
|
-
const event = createToolCallEvent({
|
|
455
|
-
ts: observedAt,
|
|
456
|
-
phase: 'started',
|
|
457
|
-
id: `forge_${this.forgeToolSequence++}`,
|
|
458
|
-
tool,
|
|
459
|
-
command: rawSummary,
|
|
460
|
-
});
|
|
461
|
-
this.forgePendingTool = {
|
|
462
|
-
id: event.id!,
|
|
463
|
-
tool: event.tool,
|
|
464
|
-
summary: event.summary,
|
|
465
|
-
summary_truncated: event.summary_truncated,
|
|
466
|
-
};
|
|
467
|
-
events.push(event);
|
|
468
|
-
continue;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
if (FORGE_FINISHED_PATTERN.test(line)) {
|
|
472
|
-
events.push(...this.completeForgePendingTool(observedAt));
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
return events;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
private extractForgeMessage(line: string, prefix: string): string | null {
|
|
481
|
-
if (!line.startsWith(prefix)) {
|
|
482
|
-
return null;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
const text = line.slice(prefix.length).trim();
|
|
486
|
-
return text || null;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
private extractParsedEvent(parsed: any, observedAt: string): PeekEvent[] {
|
|
490
|
-
if (this.agent === 'gemini') {
|
|
491
|
-
const events = this.extractGeminiParsedEvent(parsed, observedAt);
|
|
492
|
-
return events;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
return extractPeekEventsFromParsedEvent(this.agent, parsed, observedAt, this.includeToolCalls, this.toolMemory);
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
private extractGeminiParsedEvent(parsed: any, observedAt: string): PeekEvent[] {
|
|
499
|
-
if (isGeminiAssistantMessageEvent(parsed)) {
|
|
500
|
-
this.geminiAssistantBuffer += parsed.content;
|
|
501
|
-
return [];
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
const events = this.flushGeminiAssistantBuffer(observedAt);
|
|
505
|
-
|
|
506
|
-
if (this.includeToolCalls && parsed.type === 'tool_use') {
|
|
507
|
-
const event = createToolCallEvent({
|
|
508
|
-
ts: observedAt,
|
|
509
|
-
phase: 'started',
|
|
510
|
-
id: parsed.tool_id,
|
|
511
|
-
tool: parsed.tool_name || parsed.name || 'tool_use',
|
|
512
|
-
command: parsed.parameters?.command,
|
|
513
|
-
});
|
|
514
|
-
rememberToolCall(event, this.toolMemory);
|
|
515
|
-
events.push(event);
|
|
516
|
-
} else if (this.includeToolCalls && parsed.type === 'tool_result') {
|
|
517
|
-
events.push(createRememberedCompletion({
|
|
518
|
-
ts: observedAt,
|
|
519
|
-
id: parsed.tool_id,
|
|
520
|
-
memory: this.toolMemory,
|
|
521
|
-
fallbackTool: parsed.tool_name || parsed.name || 'tool_result',
|
|
522
|
-
status: parsed.status,
|
|
523
|
-
defaultStatus: 'unknown',
|
|
524
|
-
}));
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
return events;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
private flushGeminiAssistantBuffer(observedAt: string): PeekEvent[] {
|
|
531
|
-
if (this.agent !== 'gemini' || !this.geminiAssistantBuffer) {
|
|
532
|
-
return [];
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
const text = this.geminiAssistantBuffer;
|
|
536
|
-
this.geminiAssistantBuffer = '';
|
|
537
|
-
|
|
538
|
-
if (!text.trim()) {
|
|
539
|
-
return [];
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
return [{ kind: 'message', ts: observedAt, text }];
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
private completeForgePendingTool(observedAt: string): PeekEvent[] {
|
|
546
|
-
if (!this.forgePendingTool) {
|
|
547
|
-
return [];
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
const pending = this.forgePendingTool;
|
|
551
|
-
this.forgePendingTool = null;
|
|
552
|
-
const event = createToolCallEvent({
|
|
553
|
-
ts: observedAt,
|
|
554
|
-
phase: 'completed',
|
|
555
|
-
id: pending.id,
|
|
556
|
-
tool: pending.tool,
|
|
557
|
-
status: 'unknown',
|
|
558
|
-
defaultStatus: 'unknown',
|
|
559
|
-
});
|
|
560
|
-
event.summary = pending.summary;
|
|
561
|
-
if (pending.summary_truncated) {
|
|
562
|
-
event.summary_truncated = true;
|
|
563
|
-
}
|
|
564
|
-
return [event];
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
private flushForgePendingTool(observedAt: string, terminal: boolean): PeekEvent[] {
|
|
568
|
-
if (this.agent !== 'forge' || !terminal) {
|
|
569
|
-
return [];
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
return this.completeForgePendingTool(observedAt);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
export class PeekMessageExtractor {
|
|
577
|
-
private readonly extractor: PeekEventExtractor;
|
|
578
|
-
|
|
579
|
-
constructor(agent: PeekAgent) {
|
|
580
|
-
this.extractor = new PeekEventExtractor(agent, { includeToolCalls: false });
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
push(chunk: string, observedAt = new Date().toISOString()): PeekMessage[] {
|
|
584
|
-
return this.toMessages(this.extractor.push(chunk, observedAt));
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
flush(observedAt = new Date().toISOString(), options: PeekFlushOptions = {}): PeekMessage[] {
|
|
588
|
-
return this.toMessages(this.extractor.flush(observedAt, options));
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
private toMessages(events: PeekEvent[]): PeekMessage[] {
|
|
592
|
-
return events
|
|
593
|
-
.filter((event): event is Extract<PeekEvent, { kind: 'message' }> => event.kind === 'message')
|
|
594
|
-
.map((event) => ({ ts: event.ts, text: event.text }));
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
export function parseCodexOutput(stdout: string): any {
|
|
599
|
-
if (!stdout) return null;
|
|
600
|
-
|
|
601
|
-
try {
|
|
602
|
-
const lines = stdout.trim().split('\n');
|
|
603
|
-
let lastMessage = null;
|
|
604
|
-
let tokenCount = null;
|
|
605
|
-
let threadId = null;
|
|
606
|
-
const tools: any[] = [];
|
|
607
|
-
|
|
608
|
-
for (const line of lines) {
|
|
609
|
-
if (line.trim()) {
|
|
610
|
-
try {
|
|
611
|
-
const parsed = JSON.parse(line);
|
|
612
|
-
if (parsed.type === 'thread.started' && parsed.thread_id) {
|
|
613
|
-
threadId = parsed.thread_id;
|
|
614
|
-
} else if (parsed.item?.type === 'agent_message') {
|
|
615
|
-
lastMessage = parsed.item.text;
|
|
616
|
-
} else if (parsed.msg?.type === 'agent_message') {
|
|
617
|
-
lastMessage = parsed.msg.message;
|
|
618
|
-
} else if (parsed.item?.type === 'reasoning') {
|
|
619
|
-
} else if (parsed.msg?.type === 'token_count') {
|
|
620
|
-
tokenCount = parsed.msg;
|
|
621
|
-
} else if (parsed.type === 'item.completed' && parsed.item?.type === 'mcp_tool_call') {
|
|
622
|
-
tools.push({
|
|
623
|
-
server: parsed.item.server,
|
|
624
|
-
tool: parsed.item.tool,
|
|
625
|
-
input: parsed.item.arguments,
|
|
626
|
-
output: parsed.item.result
|
|
627
|
-
});
|
|
628
|
-
} else if (parsed.type === 'item.completed' && parsed.item?.type === 'command_execution') {
|
|
629
|
-
tools.push({
|
|
630
|
-
tool: 'command_execution',
|
|
631
|
-
input: { command: parsed.item.command },
|
|
632
|
-
output: parsed.item.aggregated_output,
|
|
633
|
-
exit_code: parsed.item.exit_code
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
} catch (e) {
|
|
637
|
-
debugLog(`[Debug] Skipping invalid JSON line: ${line}`);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
if (lastMessage || tokenCount || threadId || tools.length > 0) {
|
|
643
|
-
return {
|
|
644
|
-
message: lastMessage,
|
|
645
|
-
token_count: tokenCount,
|
|
646
|
-
session_id: threadId,
|
|
647
|
-
tools: tools.length > 0 ? tools : undefined
|
|
648
|
-
};
|
|
649
|
-
}
|
|
650
|
-
} catch (e) {
|
|
651
|
-
debugLog(`[Debug] Failed to parse Codex NDJSON output: ${e}`);
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
return null;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
export function parseClaudeOutput(stdout: string): any {
|
|
658
|
-
if (!stdout) return null;
|
|
659
|
-
|
|
660
|
-
try {
|
|
661
|
-
return JSON.parse(stdout);
|
|
662
|
-
} catch (e) {
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
try {
|
|
666
|
-
const lines = stdout.trim().split('\n');
|
|
667
|
-
let lastMessage = null;
|
|
668
|
-
let sessionId = null;
|
|
669
|
-
const toolsMap = new Map<string, any>();
|
|
670
|
-
|
|
671
|
-
for (const line of lines) {
|
|
672
|
-
if (!line.trim()) continue;
|
|
673
|
-
|
|
674
|
-
try {
|
|
675
|
-
const parsed = JSON.parse(line);
|
|
676
|
-
|
|
677
|
-
if (parsed.session_id) {
|
|
678
|
-
sessionId = parsed.session_id;
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
if (parsed.type === 'result' && parsed.result) {
|
|
682
|
-
lastMessage = parsed.result;
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
if (parsed.type === 'assistant' && parsed.message?.content) {
|
|
686
|
-
for (const content of parsed.message.content) {
|
|
687
|
-
if (content.type === 'tool_use') {
|
|
688
|
-
toolsMap.set(content.id, {
|
|
689
|
-
tool: content.name,
|
|
690
|
-
input: content.input,
|
|
691
|
-
output: null
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
if (parsed.type === 'user' && parsed.message?.content) {
|
|
698
|
-
for (const content of parsed.message.content) {
|
|
699
|
-
if (content.type === 'tool_result' && content.tool_use_id) {
|
|
700
|
-
const tool = toolsMap.get(content.tool_use_id);
|
|
701
|
-
if (tool) {
|
|
702
|
-
if (Array.isArray(content.content)) {
|
|
703
|
-
const textContent = content.content.find((c: any) => c.type === 'text');
|
|
704
|
-
tool.output = textContent?.text || null;
|
|
705
|
-
} else {
|
|
706
|
-
tool.output = content.content;
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
} catch (e) {
|
|
714
|
-
debugLog(`[Debug] Skipping invalid JSON line in Claude output: ${line}`);
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
const tools = Array.from(toolsMap.values());
|
|
719
|
-
|
|
720
|
-
if (lastMessage || sessionId || tools.length > 0) {
|
|
721
|
-
return {
|
|
722
|
-
message: lastMessage,
|
|
723
|
-
session_id: sessionId,
|
|
724
|
-
tools: tools.length > 0 ? tools : undefined
|
|
725
|
-
};
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
} catch (e) {
|
|
729
|
-
debugLog(`[Debug] Failed to parse Claude NDJSON output: ${e}`);
|
|
730
|
-
return null;
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
return null;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
export function parseGeminiOutput(stdout: string): any {
|
|
737
|
-
if (!stdout) return null;
|
|
738
|
-
|
|
739
|
-
try {
|
|
740
|
-
const parsed = JSON.parse(stdout.trim());
|
|
741
|
-
if (!isGeminiStreamJsonEvent(parsed)) {
|
|
742
|
-
return parsed;
|
|
743
|
-
}
|
|
744
|
-
} catch (e) {
|
|
745
|
-
debugLog(`[Debug] Failed to parse Gemini JSON output: ${e}`);
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
let sessionId: string | null = null;
|
|
749
|
-
let assistantBuffer = '';
|
|
750
|
-
let lastMessage: string | null = null;
|
|
751
|
-
let stats: any = null;
|
|
752
|
-
const toolsById = new Map<string, any>();
|
|
753
|
-
const toolsWithoutId: any[] = [];
|
|
754
|
-
const flushAssistantMessage = () => {
|
|
755
|
-
if (assistantBuffer.trim()) {
|
|
756
|
-
lastMessage = assistantBuffer;
|
|
757
|
-
}
|
|
758
|
-
assistantBuffer = '';
|
|
759
|
-
};
|
|
760
|
-
|
|
761
|
-
for (const line of stdout.split('\n')) {
|
|
762
|
-
if (!line.trim()) {
|
|
763
|
-
continue;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
let parsed: any;
|
|
767
|
-
try {
|
|
768
|
-
parsed = JSON.parse(line);
|
|
769
|
-
} catch (e) {
|
|
770
|
-
debugLog(`[Debug] Skipping invalid Gemini stream-json line: ${line}`);
|
|
771
|
-
flushAssistantMessage();
|
|
772
|
-
continue;
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
if (parsed.type === 'init' && typeof parsed.session_id === 'string' && parsed.session_id) {
|
|
776
|
-
sessionId = parsed.session_id;
|
|
777
|
-
continue;
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
if (isGeminiAssistantMessageEvent(parsed)) {
|
|
781
|
-
assistantBuffer += parsed.content;
|
|
782
|
-
continue;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
flushAssistantMessage();
|
|
786
|
-
|
|
787
|
-
if (parsed.type === 'result') {
|
|
788
|
-
if (parsed.stats) {
|
|
789
|
-
stats = parsed.stats;
|
|
790
|
-
}
|
|
791
|
-
continue;
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
if (parsed.type === 'tool_use') {
|
|
795
|
-
const tool = {
|
|
796
|
-
tool: parsed.tool_name || parsed.name || 'tool_use',
|
|
797
|
-
input: parsed.parameters ?? parsed.input ?? null,
|
|
798
|
-
output: null,
|
|
799
|
-
status: null,
|
|
800
|
-
};
|
|
801
|
-
if (typeof parsed.tool_id === 'string' && parsed.tool_id) {
|
|
802
|
-
toolsById.set(parsed.tool_id, tool);
|
|
803
|
-
} else {
|
|
804
|
-
toolsWithoutId.push(tool);
|
|
805
|
-
}
|
|
806
|
-
continue;
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
if (parsed.type === 'tool_result') {
|
|
810
|
-
const toolId = typeof parsed.tool_id === 'string' ? parsed.tool_id : '';
|
|
811
|
-
const tool = toolId ? toolsById.get(toolId) : null;
|
|
812
|
-
if (tool) {
|
|
813
|
-
tool.output = parsed.output ?? parsed.result ?? null;
|
|
814
|
-
tool.status = parsed.status ?? null;
|
|
815
|
-
} else {
|
|
816
|
-
toolsWithoutId.push({
|
|
817
|
-
tool: 'tool_result',
|
|
818
|
-
input: null,
|
|
819
|
-
output: parsed.output ?? parsed.result ?? null,
|
|
820
|
-
status: parsed.status ?? null,
|
|
821
|
-
});
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
flushAssistantMessage();
|
|
827
|
-
const tools = [...toolsById.values(), ...toolsWithoutId];
|
|
828
|
-
|
|
829
|
-
if (lastMessage || sessionId || stats || tools.length > 0) {
|
|
830
|
-
return {
|
|
831
|
-
message: lastMessage,
|
|
832
|
-
session_id: sessionId,
|
|
833
|
-
stats: stats || undefined,
|
|
834
|
-
tools: tools.length > 0 ? tools : undefined,
|
|
835
|
-
};
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
return null;
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
export function parseForgeOutput(stdout: string): any {
|
|
842
|
-
if (!stdout) return null;
|
|
843
|
-
|
|
844
|
-
const lines = stdout.split('\n');
|
|
845
|
-
const markerPattern = /^● \[[^\]]+\] (Initialize|Continue|Finished) (\S+)\s*$/;
|
|
846
|
-
let collecting = false;
|
|
847
|
-
let currentConversationId: string | null = null;
|
|
848
|
-
let currentBody: string[] = [];
|
|
849
|
-
let lastConversationId: string | null = null;
|
|
850
|
-
let lastMessage: string | null = null;
|
|
851
|
-
|
|
852
|
-
for (const line of lines) {
|
|
853
|
-
const match = line.match(markerPattern);
|
|
854
|
-
if (match) {
|
|
855
|
-
const [, action, conversationId] = match;
|
|
856
|
-
lastConversationId = conversationId;
|
|
857
|
-
|
|
858
|
-
if (action === 'Initialize' || action === 'Continue') {
|
|
859
|
-
collecting = true;
|
|
860
|
-
currentConversationId = conversationId;
|
|
861
|
-
currentBody = [];
|
|
862
|
-
} else if (collecting && currentConversationId === conversationId) {
|
|
863
|
-
const message = currentBody.join('\n').trim();
|
|
864
|
-
if (message) {
|
|
865
|
-
lastMessage = message;
|
|
866
|
-
}
|
|
867
|
-
collecting = false;
|
|
868
|
-
currentConversationId = null;
|
|
869
|
-
currentBody = [];
|
|
870
|
-
}
|
|
871
|
-
continue;
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
if (collecting) {
|
|
875
|
-
currentBody.push(line);
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
if (collecting) {
|
|
880
|
-
const message = currentBody.join('\n').trim();
|
|
881
|
-
if (message) {
|
|
882
|
-
lastMessage = message;
|
|
883
|
-
}
|
|
884
|
-
if (currentConversationId) {
|
|
885
|
-
lastConversationId = currentConversationId;
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
if (!lastMessage && !lastConversationId) {
|
|
890
|
-
return null;
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
return {
|
|
894
|
-
message: lastMessage,
|
|
895
|
-
session_id: lastConversationId,
|
|
896
|
-
};
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
export function parseOpenCodeOutput(stdout: string): any {
|
|
900
|
-
if (!stdout) {
|
|
901
|
-
return null;
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
let sessionId: string | null = null;
|
|
905
|
-
let currentStepBuffer = '';
|
|
906
|
-
let latestCompletedStep: {
|
|
907
|
-
message: string;
|
|
908
|
-
session_id?: string;
|
|
909
|
-
tokens?: any;
|
|
910
|
-
cost?: number;
|
|
911
|
-
} | null = null;
|
|
912
|
-
let hasStepFinish = false;
|
|
913
|
-
let hasParseableAssistantText = false;
|
|
914
|
-
|
|
915
|
-
for (const line of stdout.split('\n')) {
|
|
916
|
-
if (!line.trim()) {
|
|
917
|
-
continue;
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
let parsed: any;
|
|
921
|
-
try {
|
|
922
|
-
parsed = JSON.parse(line);
|
|
923
|
-
} catch {
|
|
924
|
-
continue;
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
if (typeof parsed.sessionID === 'string' && parsed.sessionID) {
|
|
928
|
-
sessionId = parsed.sessionID;
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
if (parsed.type === 'step_start') {
|
|
932
|
-
currentStepBuffer = '';
|
|
933
|
-
continue;
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
if (parsed.type === 'text' && parsed.part?.type === 'text' && typeof parsed.part.text === 'string') {
|
|
937
|
-
currentStepBuffer += parsed.part.text;
|
|
938
|
-
hasParseableAssistantText = true;
|
|
939
|
-
continue;
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
if (parsed.type === 'step_finish') {
|
|
943
|
-
hasStepFinish = true;
|
|
944
|
-
latestCompletedStep = {
|
|
945
|
-
message: currentStepBuffer,
|
|
946
|
-
session_id: sessionId || undefined,
|
|
947
|
-
tokens: parsed.part?.tokens,
|
|
948
|
-
cost: parsed.part?.cost,
|
|
949
|
-
};
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
if (hasStepFinish && latestCompletedStep) {
|
|
954
|
-
return latestCompletedStep;
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
if (hasParseableAssistantText) {
|
|
958
|
-
return {
|
|
959
|
-
message: currentStepBuffer,
|
|
960
|
-
session_id: sessionId || undefined,
|
|
961
|
-
};
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
return null;
|
|
965
|
-
}
|