@aiden-ade/sandbox-agent 0.1.5 → 0.1.6
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/core-agent.d.ts +31 -0
- package/dist/core-agent.d.ts.map +1 -0
- package/dist/core-agent.js +238 -0
- package/dist/core-agent.js.map +1 -0
- package/dist/index.cjs +81 -91
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime-backends.d.ts +20 -0
- package/dist/runtime-backends.d.ts.map +1 -0
- package/dist/runtime-backends.js +769 -0
- package/dist/runtime-backends.js.map +1 -0
- package/dist/sandbox.d.ts +14 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +139 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/types.d.ts +210 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/updater.d.ts +15 -0
- package/dist/updater.d.ts.map +1 -0
- package/dist/updater.js +50 -0
- package/dist/updater.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/dist/web-presenter.d.ts +26 -0
- package/dist/web-presenter.d.ts.map +1 -0
- package/dist/web-presenter.js +73 -0
- package/dist/web-presenter.js.map +1 -0
- package/dist/ws-client.d.ts +27 -0
- package/dist/ws-client.d.ts.map +1 -0
- package/dist/ws-client.js +56 -0
- package/dist/ws-client.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
function formatCliSpawnError(command, error) {
|
|
4
|
+
if (error.code === "ENOENT") {
|
|
5
|
+
return new Error(`CLI command not found: ${command}. Install it or configure this provider with the full executable path.`);
|
|
6
|
+
}
|
|
7
|
+
return error;
|
|
8
|
+
}
|
|
9
|
+
function buildPromptWithSystem(config, promptText) {
|
|
10
|
+
const parts = [
|
|
11
|
+
config.systemPrompt?.trim(),
|
|
12
|
+
config.systemPromptAppend?.trim(),
|
|
13
|
+
promptText.trim(),
|
|
14
|
+
].filter((value) => Boolean(value && value.length > 0));
|
|
15
|
+
return parts.join("\n\n");
|
|
16
|
+
}
|
|
17
|
+
function buildPlanModePrefix(promptText) {
|
|
18
|
+
return [
|
|
19
|
+
"You are in plan-only mode.",
|
|
20
|
+
"Do not make code changes or execute write operations.",
|
|
21
|
+
"Focus on analysis, planning, and explaining the next steps.",
|
|
22
|
+
"",
|
|
23
|
+
promptText,
|
|
24
|
+
].join("\n");
|
|
25
|
+
}
|
|
26
|
+
function spawnCli(command, args, context) {
|
|
27
|
+
const spawnWithShell = process.platform === "win32";
|
|
28
|
+
return spawn(command, args, {
|
|
29
|
+
cwd: context.cwd,
|
|
30
|
+
env: context.env,
|
|
31
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
32
|
+
shell: spawnWithShell,
|
|
33
|
+
signal: context.abortController.signal,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function createGenericCliBackend(options) {
|
|
37
|
+
return {
|
|
38
|
+
kind: options.kind,
|
|
39
|
+
supportTier: options.supportTier,
|
|
40
|
+
async run(context) {
|
|
41
|
+
const presenter = context.presenter;
|
|
42
|
+
const state = {
|
|
43
|
+
process: null,
|
|
44
|
+
iterations: 0,
|
|
45
|
+
summary: "",
|
|
46
|
+
};
|
|
47
|
+
const prompt = options.augmentPrompt?.(context) ??
|
|
48
|
+
(context.config.mode === "plan"
|
|
49
|
+
? buildPlanModePrefix(buildPromptWithSystem(context.config, context.promptText))
|
|
50
|
+
: buildPromptWithSystem(context.config, context.promptText));
|
|
51
|
+
const args = options.buildArgs?.(context, prompt) ??
|
|
52
|
+
options.args.map((arg) => (arg === "{{prompt}}" ? prompt : arg));
|
|
53
|
+
const child = spawnCli(options.command, args, context);
|
|
54
|
+
state.process = child;
|
|
55
|
+
const safeArgs = args.map((a, i) =>
|
|
56
|
+
// Redact API keys but show everything else for debugging
|
|
57
|
+
(i > 0 && args[i - 1] === "--system-prompt") ? `"<system-prompt ${a.length} chars>"` : a);
|
|
58
|
+
console.info(`[${options.kind}] Spawning: ${options.command} ${safeArgs.join(" ")}`);
|
|
59
|
+
presenter.recordRawTranscript?.("system", `${options.command} ${args.join(" ")}`, {
|
|
60
|
+
backendKind: options.kind,
|
|
61
|
+
});
|
|
62
|
+
if (options.promptViaStdin) {
|
|
63
|
+
child.stdin.write(prompt);
|
|
64
|
+
child.stdin.end();
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
child.stdin.end();
|
|
68
|
+
}
|
|
69
|
+
const stdoutRl = createInterface({ input: child.stdout });
|
|
70
|
+
const stderrRl = createInterface({ input: child.stderr });
|
|
71
|
+
const stderrLines = [];
|
|
72
|
+
stdoutRl.on("line", (line) => {
|
|
73
|
+
presenter.recordRawTranscript?.("stdout", line);
|
|
74
|
+
if (options.parseStructuredLine) {
|
|
75
|
+
options.parseStructuredLine(line, context, state);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (line.trim().length > 0) {
|
|
79
|
+
state.summary += `${line}\n`;
|
|
80
|
+
void presenter.onAssistantText(`${line}\n`);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
stderrRl.on("line", (line) => {
|
|
84
|
+
presenter.recordRawTranscript?.("stderr", line);
|
|
85
|
+
if (line.trim().length > 0) {
|
|
86
|
+
stderrLines.push(line);
|
|
87
|
+
void presenter.onLog(`[${options.kind}] ${line}`);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
const exitCode = await new Promise((resolve, reject) => {
|
|
91
|
+
child.on("error", (error) => {
|
|
92
|
+
reject(formatCliSpawnError(options.command, error));
|
|
93
|
+
});
|
|
94
|
+
child.on("exit", (code) => resolve(code ?? 0));
|
|
95
|
+
});
|
|
96
|
+
if (exitCode !== 0) {
|
|
97
|
+
console.error(`[${options.kind}] Process exited with code ${exitCode}`, {
|
|
98
|
+
stderrLines: stderrLines.slice(-10),
|
|
99
|
+
summaryPreview: state.summary.slice(0, 200),
|
|
100
|
+
error: state.error,
|
|
101
|
+
runtimeSessionId: state.runtimeSessionId,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const hasRenderableTurn = state.summary.trim().length > 0 ||
|
|
105
|
+
state.iterations > 0;
|
|
106
|
+
if (hasRenderableTurn) {
|
|
107
|
+
await presenter.onTurnComplete([]);
|
|
108
|
+
}
|
|
109
|
+
const summary = state.summary.trim() ||
|
|
110
|
+
state.error?.trim() ||
|
|
111
|
+
(exitCode === 0 ? "Task completed" : "Task failed");
|
|
112
|
+
return {
|
|
113
|
+
success: exitCode === 0,
|
|
114
|
+
summary,
|
|
115
|
+
filesModified: [],
|
|
116
|
+
planFilesCreated: [],
|
|
117
|
+
iterations: Math.max(state.iterations, 1),
|
|
118
|
+
error: exitCode === 0
|
|
119
|
+
? undefined
|
|
120
|
+
: state.error?.trim() ||
|
|
121
|
+
(stderrLines.length > 0 ? stderrLines.join("\n").trim() : undefined) ||
|
|
122
|
+
`${options.command} exited with code ${exitCode}`,
|
|
123
|
+
providerSessionId: state.runtimeSessionId,
|
|
124
|
+
runtimeSessionId: state.runtimeSessionId,
|
|
125
|
+
backendKind: options.kind,
|
|
126
|
+
supportTier: options.supportTier,
|
|
127
|
+
usage: state.usage,
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function extractToolResultContent(content) {
|
|
133
|
+
if (typeof content === "string") {
|
|
134
|
+
return content;
|
|
135
|
+
}
|
|
136
|
+
if (!Array.isArray(content)) {
|
|
137
|
+
return "";
|
|
138
|
+
}
|
|
139
|
+
return content
|
|
140
|
+
.map((block) => {
|
|
141
|
+
if (typeof block === "object" &&
|
|
142
|
+
block !== null &&
|
|
143
|
+
"type" in block &&
|
|
144
|
+
block.type === "text" &&
|
|
145
|
+
"text" in block &&
|
|
146
|
+
typeof block.text === "string") {
|
|
147
|
+
return block.text;
|
|
148
|
+
}
|
|
149
|
+
return "";
|
|
150
|
+
})
|
|
151
|
+
.filter(Boolean)
|
|
152
|
+
.join("\n");
|
|
153
|
+
}
|
|
154
|
+
function parseClaudeStructuredLine(line, context, state) {
|
|
155
|
+
const presenter = context.presenter;
|
|
156
|
+
let parsed = null;
|
|
157
|
+
try {
|
|
158
|
+
parsed = JSON.parse(line);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
state.summary += `${line}\n`;
|
|
162
|
+
void presenter.onAssistantText(`${line}\n`);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
166
|
+
if (!type)
|
|
167
|
+
return;
|
|
168
|
+
if (typeof parsed.session_id === "string") {
|
|
169
|
+
state.runtimeSessionId = parsed.session_id;
|
|
170
|
+
}
|
|
171
|
+
switch (type) {
|
|
172
|
+
case "system": {
|
|
173
|
+
if (parsed.subtype === "init") {
|
|
174
|
+
const model = typeof parsed.model === "string" ? parsed.model : "claude";
|
|
175
|
+
void presenter.onLog(`[claude_cli] Session initialized with ${model}`);
|
|
176
|
+
}
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
case "assistant": {
|
|
180
|
+
state.iterations += 1;
|
|
181
|
+
const message = typeof parsed.message === "object" && parsed.message !== null
|
|
182
|
+
? parsed.message
|
|
183
|
+
: null;
|
|
184
|
+
const content = Array.isArray(message?.content) ? message.content : [];
|
|
185
|
+
for (const block of content) {
|
|
186
|
+
if (typeof block !== "object" || block === null || !("type" in block)) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
190
|
+
const text = block.text;
|
|
191
|
+
state.summary += `${text}\n`;
|
|
192
|
+
void presenter.onAssistantText(text);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (block.type === "thinking" &&
|
|
196
|
+
typeof block.thinking === "string") {
|
|
197
|
+
void presenter.onThinking(block.thinking);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (block.type === "tool_use" &&
|
|
201
|
+
typeof block.id === "string" &&
|
|
202
|
+
typeof block.name === "string") {
|
|
203
|
+
const toolBlock = block;
|
|
204
|
+
void presenter.onToolUse(toolBlock.name, toolBlock.input ?? {}, toolBlock.id);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case "user": {
|
|
210
|
+
const message = typeof parsed.message === "object" && parsed.message !== null
|
|
211
|
+
? parsed.message
|
|
212
|
+
: null;
|
|
213
|
+
const content = Array.isArray(message?.content) ? message.content : [];
|
|
214
|
+
for (const block of content) {
|
|
215
|
+
if (typeof block === "object" &&
|
|
216
|
+
block !== null &&
|
|
217
|
+
"type" in block &&
|
|
218
|
+
block.type === "tool_result" &&
|
|
219
|
+
"tool_use_id" in block &&
|
|
220
|
+
typeof block.tool_use_id === "string") {
|
|
221
|
+
void presenter.onToolResult?.(block.tool_use_id, extractToolResultContent(block.content));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
case "result": {
|
|
227
|
+
const inputTokens = typeof parsed.usage === "object" &&
|
|
228
|
+
parsed.usage !== null &&
|
|
229
|
+
typeof parsed.usage.input_tokens === "number"
|
|
230
|
+
? parsed.usage.input_tokens
|
|
231
|
+
: 0;
|
|
232
|
+
const outputTokens = typeof parsed.usage === "object" &&
|
|
233
|
+
parsed.usage !== null &&
|
|
234
|
+
typeof parsed.usage.output_tokens === "number"
|
|
235
|
+
? parsed.usage.output_tokens
|
|
236
|
+
: 0;
|
|
237
|
+
const cacheReadTokens = typeof parsed.usage === "object" &&
|
|
238
|
+
parsed.usage !== null &&
|
|
239
|
+
typeof parsed.usage.cache_read_input_tokens === "number"
|
|
240
|
+
? parsed.usage.cache_read_input_tokens
|
|
241
|
+
: 0;
|
|
242
|
+
const cacheCreationTokens = typeof parsed.usage === "object" &&
|
|
243
|
+
parsed.usage !== null &&
|
|
244
|
+
typeof parsed.usage.cache_creation_input_tokens === "number"
|
|
245
|
+
? parsed.usage.cache_creation_input_tokens
|
|
246
|
+
: 0;
|
|
247
|
+
state.usage = {
|
|
248
|
+
model: typeof parsed.model === "string"
|
|
249
|
+
? parsed.model
|
|
250
|
+
: typeof context.config.selectedModel === "string"
|
|
251
|
+
? context.config.selectedModel
|
|
252
|
+
: "claude",
|
|
253
|
+
numTurns: typeof parsed.num_turns === "number" ? parsed.num_turns : Math.max(state.iterations, 1),
|
|
254
|
+
durationMs: typeof parsed.duration_ms === "number" ? parsed.duration_ms : 0,
|
|
255
|
+
inputTokens,
|
|
256
|
+
outputTokens,
|
|
257
|
+
cacheReadTokens,
|
|
258
|
+
cacheCreationTokens,
|
|
259
|
+
costUsd: typeof parsed.total_cost_usd === "number" ? parsed.total_cost_usd : 0,
|
|
260
|
+
};
|
|
261
|
+
void presenter.onUsageUpdate?.({
|
|
262
|
+
model: state.usage.model,
|
|
263
|
+
inputTokens: state.usage.inputTokens,
|
|
264
|
+
outputTokens: state.usage.outputTokens,
|
|
265
|
+
cacheReadTokens: state.usage.cacheReadTokens,
|
|
266
|
+
cacheCreationTokens: state.usage.cacheCreationTokens,
|
|
267
|
+
});
|
|
268
|
+
if (typeof parsed.result === "string" && parsed.result.trim().length > 0) {
|
|
269
|
+
state.summary = parsed.result.trim();
|
|
270
|
+
}
|
|
271
|
+
if (typeof parsed.num_turns === "number") {
|
|
272
|
+
state.iterations = parsed.num_turns;
|
|
273
|
+
}
|
|
274
|
+
const subtype = typeof parsed.subtype === "string" ? parsed.subtype : "";
|
|
275
|
+
if ((subtype === "error_during_execution" || subtype === "error_max_turns") &&
|
|
276
|
+
!state.error) {
|
|
277
|
+
const errObj = typeof parsed.error === "object" && parsed.error !== null
|
|
278
|
+
? parsed.error
|
|
279
|
+
: null;
|
|
280
|
+
const errMsg = (typeof parsed.error === "string" && parsed.error.trim()) ||
|
|
281
|
+
(errObj && typeof errObj.message === "string" && errObj.message.trim()) ||
|
|
282
|
+
(typeof parsed.message === "string" && parsed.message.trim()) ||
|
|
283
|
+
(typeof parsed.result === "string" && parsed.result.trim()) ||
|
|
284
|
+
subtype;
|
|
285
|
+
state.error = errMsg;
|
|
286
|
+
const errFields = {
|
|
287
|
+
error: parsed.error,
|
|
288
|
+
message: parsed.message,
|
|
289
|
+
result: typeof parsed.result === "string" ? parsed.result.slice(0, 200) : parsed.result,
|
|
290
|
+
session_id: parsed.session_id,
|
|
291
|
+
model: parsed.model,
|
|
292
|
+
num_turns: parsed.num_turns,
|
|
293
|
+
is_error: parsed.is_error,
|
|
294
|
+
};
|
|
295
|
+
console.error("[claude_cli] result subtype=", subtype, "error fields:", JSON.stringify(errFields));
|
|
296
|
+
console.error("[claude_cli] Full result event:", JSON.stringify(parsed).slice(0, 2000));
|
|
297
|
+
}
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
default:
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function parseCodexStructuredLine(line, context, state) {
|
|
305
|
+
const presenter = context.presenter;
|
|
306
|
+
let parsed = null;
|
|
307
|
+
try {
|
|
308
|
+
parsed = JSON.parse(line);
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
state.summary += `${line}\n`;
|
|
312
|
+
void presenter.onAssistantText(`${line}\n`);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
316
|
+
if (!type)
|
|
317
|
+
return;
|
|
318
|
+
if (typeof parsed.thread_id === "string") {
|
|
319
|
+
state.runtimeSessionId = parsed.thread_id;
|
|
320
|
+
}
|
|
321
|
+
const extractCodexErrorMessage = (value) => {
|
|
322
|
+
if (typeof value !== "string") {
|
|
323
|
+
return "";
|
|
324
|
+
}
|
|
325
|
+
const trimmed = value.trim();
|
|
326
|
+
if (!trimmed) {
|
|
327
|
+
return "";
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
const parsedValue = JSON.parse(trimmed);
|
|
331
|
+
if (typeof parsedValue.detail === "string" && parsedValue.detail.trim().length > 0) {
|
|
332
|
+
return parsedValue.detail.trim();
|
|
333
|
+
}
|
|
334
|
+
if (typeof parsedValue.message === "string" && parsedValue.message.trim().length > 0) {
|
|
335
|
+
return parsedValue.message.trim();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
// Fall through to the raw string below.
|
|
340
|
+
}
|
|
341
|
+
return trimmed;
|
|
342
|
+
};
|
|
343
|
+
switch (type) {
|
|
344
|
+
case "thread.started":
|
|
345
|
+
break;
|
|
346
|
+
case "turn.started":
|
|
347
|
+
state.iterations += 1;
|
|
348
|
+
break;
|
|
349
|
+
case "session_configured":
|
|
350
|
+
if (typeof parsed.session_id === "string") {
|
|
351
|
+
state.runtimeSessionId = parsed.session_id;
|
|
352
|
+
}
|
|
353
|
+
break;
|
|
354
|
+
case "task_started":
|
|
355
|
+
state.iterations += 1;
|
|
356
|
+
break;
|
|
357
|
+
case "item.started": {
|
|
358
|
+
const item = typeof parsed.item === "object" && parsed.item !== null
|
|
359
|
+
? parsed.item
|
|
360
|
+
: null;
|
|
361
|
+
if (!item || item.type !== "command_execution" || typeof item.id !== "string") {
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
const command = typeof item.command === "string" ? item.command : "";
|
|
365
|
+
void presenter.onToolUse("Bash", { command, cwd: context.cwd }, item.id);
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
case "item.completed": {
|
|
369
|
+
const item = typeof parsed.item === "object" && parsed.item !== null
|
|
370
|
+
? parsed.item
|
|
371
|
+
: null;
|
|
372
|
+
if (!item || typeof item.type !== "string") {
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
if (item.type === "reasoning" && typeof item.text === "string") {
|
|
376
|
+
void presenter.onThinking(item.text);
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
if (item.type === "agent_message" && typeof item.text === "string") {
|
|
380
|
+
state.summary += `${item.text}\n`;
|
|
381
|
+
void presenter.onAssistantText(item.text);
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
if (item.type === "command_execution" && typeof item.id === "string") {
|
|
385
|
+
const output = typeof item.aggregated_output === "string" ? item.aggregated_output : "";
|
|
386
|
+
const exitCode = typeof item.exit_code === "number" ? item.exit_code : null;
|
|
387
|
+
const resultText = output.trim().length > 0
|
|
388
|
+
? output
|
|
389
|
+
: exitCode === null
|
|
390
|
+
? ""
|
|
391
|
+
: `Exit code: ${exitCode}`;
|
|
392
|
+
void presenter.onToolResult?.(item.id, resultText);
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
case "agent_message_delta":
|
|
398
|
+
case "agent_message_content_delta": {
|
|
399
|
+
const delta = typeof parsed.delta === "string" ? parsed.delta : "";
|
|
400
|
+
if (!delta)
|
|
401
|
+
return;
|
|
402
|
+
state.summary += delta;
|
|
403
|
+
void presenter.onAssistantText(delta);
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
case "agent_reasoning_delta":
|
|
407
|
+
case "reasoning_content_delta":
|
|
408
|
+
case "agent_reasoning_raw_content_delta": {
|
|
409
|
+
const delta = typeof parsed.delta === "string" ? parsed.delta : "";
|
|
410
|
+
if (!delta)
|
|
411
|
+
return;
|
|
412
|
+
void presenter.onThinking(delta);
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
case "exec_command_begin": {
|
|
416
|
+
const toolId = typeof parsed.call_id === "string" ? parsed.call_id : `exec-${Date.now()}`;
|
|
417
|
+
const command = Array.isArray(parsed.command) ? parsed.command.join(" ") : "";
|
|
418
|
+
const cwd = typeof parsed.cwd === "string" ? parsed.cwd : context.cwd;
|
|
419
|
+
void presenter.onToolUse("Bash", { command, cwd }, toolId);
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
case "exec_command_end": {
|
|
423
|
+
const toolId = typeof parsed.call_id === "string" ? parsed.call_id : `exec-${Date.now()}`;
|
|
424
|
+
const output = typeof parsed.formatted_output === "string"
|
|
425
|
+
? parsed.formatted_output
|
|
426
|
+
: typeof parsed.aggregated_output === "string"
|
|
427
|
+
? parsed.aggregated_output
|
|
428
|
+
: "";
|
|
429
|
+
void presenter.onToolResult?.(toolId, output);
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
case "mcp_tool_call_begin": {
|
|
433
|
+
const toolId = typeof parsed.call_id === "string" ? parsed.call_id : `mcp-${Date.now()}`;
|
|
434
|
+
const invocation = typeof parsed.invocation === "object" && parsed.invocation !== null
|
|
435
|
+
? parsed.invocation
|
|
436
|
+
: {};
|
|
437
|
+
const tool = typeof invocation.tool_name === "string"
|
|
438
|
+
? invocation.tool_name
|
|
439
|
+
: typeof invocation.tool === "string"
|
|
440
|
+
? invocation.tool
|
|
441
|
+
: "MCP Tool";
|
|
442
|
+
void presenter.onToolUse(tool, invocation, toolId);
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
445
|
+
case "mcp_tool_call_end": {
|
|
446
|
+
const toolId = typeof parsed.call_id === "string" ? parsed.call_id : `mcp-${Date.now()}`;
|
|
447
|
+
const result = parsed.result;
|
|
448
|
+
void presenter.onToolResult?.(toolId, JSON.stringify(result ?? {}, null, 2));
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
case "token_count": {
|
|
452
|
+
const info = typeof parsed.info === "object" && parsed.info !== null
|
|
453
|
+
? parsed.info
|
|
454
|
+
: {};
|
|
455
|
+
const inputTokens = typeof info.input_tokens === "number" ? info.input_tokens : 0;
|
|
456
|
+
const outputTokens = typeof info.output_tokens === "number" ? info.output_tokens : 0;
|
|
457
|
+
state.usage = {
|
|
458
|
+
model: typeof info.model === "string" ? info.model : "codex",
|
|
459
|
+
numTurns: Math.max(state.iterations, 1),
|
|
460
|
+
durationMs: 0,
|
|
461
|
+
inputTokens,
|
|
462
|
+
outputTokens,
|
|
463
|
+
cacheReadTokens: typeof info.cached_input_tokens === "number" ? info.cached_input_tokens : 0,
|
|
464
|
+
cacheCreationTokens: 0,
|
|
465
|
+
costUsd: typeof info.total_cost_usd === "number" ? info.total_cost_usd : 0,
|
|
466
|
+
};
|
|
467
|
+
void presenter.onUsageUpdate?.({
|
|
468
|
+
model: state.usage.model,
|
|
469
|
+
inputTokens: state.usage.inputTokens,
|
|
470
|
+
outputTokens: state.usage.outputTokens,
|
|
471
|
+
cacheReadTokens: state.usage.cacheReadTokens,
|
|
472
|
+
cacheCreationTokens: state.usage.cacheCreationTokens,
|
|
473
|
+
});
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
case "task_complete": {
|
|
477
|
+
const lastMessage = typeof parsed.last_agent_message === "string" ? parsed.last_agent_message : "";
|
|
478
|
+
if (lastMessage.length > 0) {
|
|
479
|
+
state.summary = lastMessage;
|
|
480
|
+
}
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
case "turn.completed": {
|
|
484
|
+
const usage = typeof parsed.usage === "object" && parsed.usage !== null
|
|
485
|
+
? parsed.usage
|
|
486
|
+
: {};
|
|
487
|
+
const inputTokens = typeof usage.input_tokens === "number" ? usage.input_tokens : 0;
|
|
488
|
+
const outputTokens = typeof usage.output_tokens === "number" ? usage.output_tokens : 0;
|
|
489
|
+
const cacheReadTokens = typeof usage.cached_input_tokens === "number" ? usage.cached_input_tokens : 0;
|
|
490
|
+
state.usage = {
|
|
491
|
+
model: typeof context.config.selectedModel === "string" && context.config.selectedModel.trim()
|
|
492
|
+
? context.config.selectedModel
|
|
493
|
+
: "codex",
|
|
494
|
+
numTurns: Math.max(state.iterations, 1),
|
|
495
|
+
durationMs: 0,
|
|
496
|
+
inputTokens,
|
|
497
|
+
outputTokens,
|
|
498
|
+
cacheReadTokens,
|
|
499
|
+
cacheCreationTokens: 0,
|
|
500
|
+
costUsd: 0,
|
|
501
|
+
};
|
|
502
|
+
void presenter.onUsageUpdate?.({
|
|
503
|
+
model: state.usage.model,
|
|
504
|
+
inputTokens: state.usage.inputTokens,
|
|
505
|
+
outputTokens: state.usage.outputTokens,
|
|
506
|
+
cacheReadTokens: state.usage.cacheReadTokens,
|
|
507
|
+
cacheCreationTokens: state.usage.cacheCreationTokens,
|
|
508
|
+
});
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
case "error": {
|
|
512
|
+
const message = extractCodexErrorMessage(parsed.message);
|
|
513
|
+
if (message) {
|
|
514
|
+
state.error = message;
|
|
515
|
+
}
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
case "turn.failed": {
|
|
519
|
+
const error = typeof parsed.error === "object" && parsed.error !== null
|
|
520
|
+
? parsed.error
|
|
521
|
+
: {};
|
|
522
|
+
const message = extractCodexErrorMessage(error.message);
|
|
523
|
+
if (message) {
|
|
524
|
+
state.error = message;
|
|
525
|
+
}
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
default:
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function buildGeminiToolResultText(parsed) {
|
|
533
|
+
const output = typeof parsed.output === "string" ? parsed.output : "";
|
|
534
|
+
if (output.trim().length > 0) {
|
|
535
|
+
return output;
|
|
536
|
+
}
|
|
537
|
+
const error = typeof parsed.error === "object" && parsed.error !== null
|
|
538
|
+
? parsed.error
|
|
539
|
+
: null;
|
|
540
|
+
const message = typeof error?.message === "string" ? error.message.trim() : "";
|
|
541
|
+
const errorType = typeof error?.type === "string" ? error.type.trim() : "";
|
|
542
|
+
if (message && errorType) {
|
|
543
|
+
return `${errorType}: ${message}`;
|
|
544
|
+
}
|
|
545
|
+
if (message) {
|
|
546
|
+
return message;
|
|
547
|
+
}
|
|
548
|
+
const status = typeof parsed.status === "string" ? parsed.status.trim() : "";
|
|
549
|
+
return status || "Tool completed";
|
|
550
|
+
}
|
|
551
|
+
function parseGeminiStructuredLine(line, context, state) {
|
|
552
|
+
const presenter = context.presenter;
|
|
553
|
+
let parsed = null;
|
|
554
|
+
try {
|
|
555
|
+
parsed = JSON.parse(line);
|
|
556
|
+
}
|
|
557
|
+
catch {
|
|
558
|
+
state.summary += `${line}\n`;
|
|
559
|
+
void presenter.onAssistantText(`${line}\n`);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
563
|
+
if (!type) {
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (typeof parsed.session_id === "string") {
|
|
567
|
+
state.runtimeSessionId = parsed.session_id;
|
|
568
|
+
}
|
|
569
|
+
if (typeof parsed.model === "string") {
|
|
570
|
+
state.activeModel = parsed.model;
|
|
571
|
+
}
|
|
572
|
+
switch (type) {
|
|
573
|
+
case "init": {
|
|
574
|
+
const model = state.activeModel || context.config.selectedModel || "gemini";
|
|
575
|
+
void presenter.onLog(`[gemini_cli] Session initialized with ${model}`);
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
case "message": {
|
|
579
|
+
const role = typeof parsed.role === "string" ? parsed.role : "";
|
|
580
|
+
if (role !== "assistant") {
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
const content = typeof parsed.content === "string" ? parsed.content : "";
|
|
584
|
+
if (!content) {
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
state.iterations = Math.max(state.iterations, 1);
|
|
588
|
+
state.summary += content;
|
|
589
|
+
void presenter.onAssistantText(content);
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
case "tool_use": {
|
|
593
|
+
const toolName = typeof parsed.tool_name === "string" ? parsed.tool_name : "Gemini Tool";
|
|
594
|
+
const toolId = typeof parsed.tool_id === "string" ? parsed.tool_id : `gemini-tool-${Date.now()}`;
|
|
595
|
+
state.iterations = Math.max(state.iterations, 1);
|
|
596
|
+
void presenter.onToolUse(toolName, parsed.parameters ?? {}, toolId);
|
|
597
|
+
break;
|
|
598
|
+
}
|
|
599
|
+
case "tool_result": {
|
|
600
|
+
const toolId = typeof parsed.tool_id === "string" ? parsed.tool_id : `gemini-tool-${Date.now()}`;
|
|
601
|
+
state.iterations = Math.max(state.iterations, 1);
|
|
602
|
+
void presenter.onToolResult?.(toolId, buildGeminiToolResultText(parsed));
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
case "error": {
|
|
606
|
+
const message = typeof parsed.message === "string" ? parsed.message.trim() : "";
|
|
607
|
+
const severity = typeof parsed.severity === "string" ? parsed.severity : "error";
|
|
608
|
+
if (message) {
|
|
609
|
+
void presenter.onLog(`[gemini_cli/${severity}] ${message}`);
|
|
610
|
+
if (severity === "error") {
|
|
611
|
+
state.error = message;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
case "result": {
|
|
617
|
+
const stats = typeof parsed.stats === "object" && parsed.stats !== null
|
|
618
|
+
? parsed.stats
|
|
619
|
+
: {};
|
|
620
|
+
state.iterations = Math.max(state.iterations, 1);
|
|
621
|
+
state.usage = {
|
|
622
|
+
model: state.activeModel ||
|
|
623
|
+
(typeof context.config.selectedModel === "string" && context.config.selectedModel.trim()
|
|
624
|
+
? context.config.selectedModel
|
|
625
|
+
: "gemini"),
|
|
626
|
+
numTurns: Math.max(state.iterations, 1),
|
|
627
|
+
durationMs: typeof stats.duration_ms === "number" ? stats.duration_ms : 0,
|
|
628
|
+
inputTokens: typeof stats.input_tokens === "number"
|
|
629
|
+
? stats.input_tokens
|
|
630
|
+
: typeof stats.input === "number"
|
|
631
|
+
? stats.input
|
|
632
|
+
: 0,
|
|
633
|
+
outputTokens: typeof stats.output_tokens === "number" ? stats.output_tokens : 0,
|
|
634
|
+
cacheReadTokens: typeof stats.cached === "number" ? stats.cached : 0,
|
|
635
|
+
cacheCreationTokens: 0,
|
|
636
|
+
costUsd: 0,
|
|
637
|
+
};
|
|
638
|
+
void presenter.onUsageUpdate?.({
|
|
639
|
+
model: state.usage.model,
|
|
640
|
+
inputTokens: state.usage.inputTokens,
|
|
641
|
+
outputTokens: state.usage.outputTokens,
|
|
642
|
+
cacheReadTokens: state.usage.cacheReadTokens,
|
|
643
|
+
cacheCreationTokens: state.usage.cacheCreationTokens,
|
|
644
|
+
});
|
|
645
|
+
if (typeof parsed.status === "string" && parsed.status !== "success" && !state.error) {
|
|
646
|
+
state.error = parsed.status;
|
|
647
|
+
}
|
|
648
|
+
break;
|
|
649
|
+
}
|
|
650
|
+
default:
|
|
651
|
+
break;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
function getClaudePermissionMode(mode) {
|
|
655
|
+
switch (mode) {
|
|
656
|
+
case "plan":
|
|
657
|
+
case "ask":
|
|
658
|
+
case "review":
|
|
659
|
+
return "plan";
|
|
660
|
+
default:
|
|
661
|
+
return "bypassPermissions";
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
export function createClaudeCliBackend(command = "claude", defaultArgs = []) {
|
|
665
|
+
return {
|
|
666
|
+
kind: "claude_cli",
|
|
667
|
+
supportTier: "structured",
|
|
668
|
+
async run(context) {
|
|
669
|
+
const args = [
|
|
670
|
+
"--print",
|
|
671
|
+
"--verbose",
|
|
672
|
+
"--output-format",
|
|
673
|
+
"stream-json",
|
|
674
|
+
"--permission-mode",
|
|
675
|
+
getClaudePermissionMode(context.config.mode),
|
|
676
|
+
];
|
|
677
|
+
if (context.config.systemPrompt?.trim()) {
|
|
678
|
+
args.push("--system-prompt", context.config.systemPrompt.trim());
|
|
679
|
+
}
|
|
680
|
+
if (context.config.systemPromptAppend?.trim()) {
|
|
681
|
+
args.push("--append-system-prompt", context.config.systemPromptAppend.trim());
|
|
682
|
+
}
|
|
683
|
+
if (context.config.selectedModel?.trim()) {
|
|
684
|
+
args.push("--model", context.config.selectedModel.trim());
|
|
685
|
+
}
|
|
686
|
+
const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
687
|
+
if (resumeId) {
|
|
688
|
+
args.push("--resume", resumeId);
|
|
689
|
+
console.info("[claude_cli] Resuming session", { resumeId });
|
|
690
|
+
}
|
|
691
|
+
args.push(...defaultArgs, context.promptText);
|
|
692
|
+
return createGenericCliBackend({
|
|
693
|
+
kind: "claude_cli",
|
|
694
|
+
supportTier: "structured",
|
|
695
|
+
command,
|
|
696
|
+
args,
|
|
697
|
+
parseStructuredLine: parseClaudeStructuredLine,
|
|
698
|
+
}).run(context);
|
|
699
|
+
},
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
export function createGeminiCliBackend(command = "gemini", defaultArgs = []) {
|
|
703
|
+
return createGenericCliBackend({
|
|
704
|
+
kind: "gemini_cli",
|
|
705
|
+
supportTier: "structured",
|
|
706
|
+
command,
|
|
707
|
+
args: [],
|
|
708
|
+
buildArgs: (context) => {
|
|
709
|
+
const args = ["-p", "", "--output-format", "stream-json"];
|
|
710
|
+
const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
711
|
+
const approvalMode = context.config.mode === "plan" ||
|
|
712
|
+
context.config.mode === "ask" ||
|
|
713
|
+
context.config.mode === "review"
|
|
714
|
+
? "plan"
|
|
715
|
+
: "yolo";
|
|
716
|
+
args.push("--approval-mode", approvalMode);
|
|
717
|
+
if (resumeId) {
|
|
718
|
+
args.push("--resume", resumeId);
|
|
719
|
+
}
|
|
720
|
+
if (context.config.selectedModel?.trim()) {
|
|
721
|
+
args.push("--model", context.config.selectedModel.trim());
|
|
722
|
+
}
|
|
723
|
+
return [...args, ...defaultArgs];
|
|
724
|
+
},
|
|
725
|
+
promptViaStdin: true,
|
|
726
|
+
augmentPrompt: (context) => {
|
|
727
|
+
const basePrompt = buildPromptWithSystem(context.config, context.promptText);
|
|
728
|
+
if (context.config.mode === "plan") {
|
|
729
|
+
return buildPlanModePrefix(basePrompt);
|
|
730
|
+
}
|
|
731
|
+
return basePrompt;
|
|
732
|
+
},
|
|
733
|
+
parseStructuredLine: parseGeminiStructuredLine,
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
export function createGenericCliPassthroughBackend(command, defaultArgs = []) {
|
|
737
|
+
return createGenericCliBackend({
|
|
738
|
+
kind: "generic_cli",
|
|
739
|
+
supportTier: "text",
|
|
740
|
+
command,
|
|
741
|
+
args: defaultArgs,
|
|
742
|
+
promptViaStdin: true,
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
export function createCodexRuntimeBackend(command = "codex", defaultArgs = []) {
|
|
746
|
+
return createGenericCliBackend({
|
|
747
|
+
kind: "codex_app_server",
|
|
748
|
+
supportTier: "structured",
|
|
749
|
+
command,
|
|
750
|
+
args: [],
|
|
751
|
+
buildArgs: (context) => {
|
|
752
|
+
const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
753
|
+
const baseArgs = resumeId
|
|
754
|
+
? ["exec", "resume", "--json", "--skip-git-repo-check", resumeId, "-"]
|
|
755
|
+
: ["exec", "--json", "--skip-git-repo-check", "-"];
|
|
756
|
+
return [...baseArgs, ...defaultArgs];
|
|
757
|
+
},
|
|
758
|
+
promptViaStdin: true,
|
|
759
|
+
augmentPrompt: (context) => {
|
|
760
|
+
const basePrompt = buildPromptWithSystem(context.config, context.promptText);
|
|
761
|
+
if (context.config.mode === "plan") {
|
|
762
|
+
return buildPlanModePrefix(basePrompt);
|
|
763
|
+
}
|
|
764
|
+
return basePrompt;
|
|
765
|
+
},
|
|
766
|
+
parseStructuredLine: parseCodexStructuredLine,
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
//# sourceMappingURL=runtime-backends.js.map
|