substrate-ai 0.9.0 → 0.11.0
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/adapter-registry-DXLMTmfD.js +0 -0
- package/dist/adapter-registry-neBZrkr3.js +4 -0
- package/dist/cli/index.js +5594 -5951
- package/dist/decisions-C0pz9Clx.js +0 -0
- package/dist/{decisions-BDLp3tJB.js → decisions-DQZW0h9X.js} +2 -1
- package/dist/dist-eNB_v7Iy.js +10205 -0
- package/dist/errors-BvyMlvCX.js +74 -0
- package/dist/experimenter-Dos3NsCg.js +3 -0
- package/dist/health-BvYILeQQ.js +6 -0
- package/dist/{health-C-VRJruD.js → health-CiDi90gC.js} +57 -1850
- package/dist/{helpers-CpMs8VZX.js → helpers-DTp3VJ2-.js} +31 -121
- package/dist/index.d.ts +709 -266
- package/dist/index.js +5 -3
- package/dist/{logger-D2fS2ccL.js → logger-KeHncl-f.js} +2 -42
- package/dist/routing-CcBOCuC9.js +0 -0
- package/dist/{routing-CD8bIci_.js → routing-HaYsjEIS.js} +2 -2
- package/dist/{run-ClxNDHbr.js → run-CAUhTR7Y.js} +594 -4249
- package/dist/run-DPZOQOvB.js +9 -0
- package/dist/{upgrade-B1S61VXJ.js → upgrade-DFGrqjGI.js} +3 -3
- package/dist/{upgrade-BK0HrKA6.js → upgrade-DYdYuuJK.js} +3 -3
- package/dist/version-manager-impl-BmOWu8ml.js +0 -0
- package/dist/version-manager-impl-CKv6I1S0.js +4 -0
- package/package.json +5 -2
- package/dist/adapter-registry-D2zdMwVu.js +0 -840
- package/dist/adapter-registry-WAyFydN5.js +0 -4
- package/dist/config-migrator-CtGelIsG.js +0 -250
- package/dist/decisions-DhAA2HG2.js +0 -397
- package/dist/experimenter-D_N_7ZF3.js +0 -503
- package/dist/git-utils-DxPx6erV.js +0 -365
- package/dist/health-DMbNP9bw.js +0 -5
- package/dist/operational-BdcdmDqS.js +0 -374
- package/dist/routing-BVrxrM6v.js +0 -832
- package/dist/run-MAQ3Wuju.js +0 -10
- package/dist/version-manager-impl-BIxOe7gZ.js +0 -372
- package/dist/version-manager-impl-RrWs-CI6.js +0 -4
|
@@ -1,840 +0,0 @@
|
|
|
1
|
-
import { createLogger } from "./logger-D2fS2ccL.js";
|
|
2
|
-
import { exec } from "child_process";
|
|
3
|
-
import { promisify } from "util";
|
|
4
|
-
|
|
5
|
-
//#region src/adapters/claude-adapter.ts
|
|
6
|
-
const execAsync$2 = promisify(exec);
|
|
7
|
-
const logger = createLogger("claude-adapter");
|
|
8
|
-
/** Default model used when none is specified */
|
|
9
|
-
const DEFAULT_MODEL$1 = "claude-sonnet-4-6";
|
|
10
|
-
/** Approximate characters per token for estimation */
|
|
11
|
-
const CHARS_PER_TOKEN$2 = 3;
|
|
12
|
-
/** Estimated output token multiplier relative to input */
|
|
13
|
-
const OUTPUT_RATIO$2 = .5;
|
|
14
|
-
/** Strip markdown code fences from LLM output (e.g. ```json ... ```) */
|
|
15
|
-
function stripCodeFences$2(raw) {
|
|
16
|
-
return raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```\s*$/, "").trim();
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Adapter for the Claude Code CLI agent.
|
|
20
|
-
*
|
|
21
|
-
* Capabilities: JSON output, streaming, both billing modes, plan generation.
|
|
22
|
-
* Health check: runs `claude --version` to verify install.
|
|
23
|
-
* Billing detection: detects subscription vs API via version output or env.
|
|
24
|
-
*/
|
|
25
|
-
var ClaudeCodeAdapter = class {
|
|
26
|
-
id = "claude-code";
|
|
27
|
-
displayName = "Claude Code";
|
|
28
|
-
adapterVersion = "1.0.0";
|
|
29
|
-
/**
|
|
30
|
-
* Verify the `claude` binary is installed and responsive.
|
|
31
|
-
* Detects subscription vs API billing mode.
|
|
32
|
-
*/
|
|
33
|
-
async healthCheck() {
|
|
34
|
-
try {
|
|
35
|
-
const { stdout } = await execAsync$2("claude --version", { timeout: 1e4 });
|
|
36
|
-
const output = stdout.trim();
|
|
37
|
-
const detectedBillingModes = this._detectBillingModes(output);
|
|
38
|
-
let cliPath;
|
|
39
|
-
try {
|
|
40
|
-
const whichResult = await execAsync$2("which claude", { timeout: 5e3 });
|
|
41
|
-
cliPath = whichResult.stdout.trim();
|
|
42
|
-
} catch {}
|
|
43
|
-
return {
|
|
44
|
-
healthy: true,
|
|
45
|
-
version: output,
|
|
46
|
-
...cliPath !== void 0 ? { cliPath } : {},
|
|
47
|
-
detectedBillingModes,
|
|
48
|
-
supportsHeadless: true
|
|
49
|
-
};
|
|
50
|
-
} catch (err) {
|
|
51
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
52
|
-
return {
|
|
53
|
-
healthy: false,
|
|
54
|
-
error: `Claude CLI not available: ${message}`,
|
|
55
|
-
supportsHeadless: false
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Build spawn command for a coding task.
|
|
61
|
-
* Uses: `claude -p --model <model> --dangerously-skip-permissions --system-prompt <minimal>`
|
|
62
|
-
* Prompt is delivered via stdin (not CLI arg) to avoid E2BIG on large prompts.
|
|
63
|
-
*/
|
|
64
|
-
buildCommand(prompt, options) {
|
|
65
|
-
const model = options.model ?? DEFAULT_MODEL$1;
|
|
66
|
-
const systemPrompt = "You are an autonomous coding agent executing a single pipeline task. Ignore all session startup context, memory notes, and \"Next Up\" indicators. Follow the instructions in the user message exactly. Emit ONLY the YAML output specified in the Output Contract — no other text.";
|
|
67
|
-
const effectiveSystemPrompt = options.optimizationDirectives !== void 0 && options.optimizationDirectives.length > 0 ? `${systemPrompt}\n\n## Optimization Directives\n${options.optimizationDirectives}` : systemPrompt;
|
|
68
|
-
if (options.optimizationDirectives !== void 0 && options.optimizationDirectives.length > 0) logger.debug({
|
|
69
|
-
storyKey: options.storyKey,
|
|
70
|
-
directiveChars: options.optimizationDirectives.length
|
|
71
|
-
}, "Injecting optimization directives into system prompt");
|
|
72
|
-
const args = [
|
|
73
|
-
"-p",
|
|
74
|
-
"--model",
|
|
75
|
-
model,
|
|
76
|
-
"--dangerously-skip-permissions",
|
|
77
|
-
"--system-prompt",
|
|
78
|
-
effectiveSystemPrompt
|
|
79
|
-
];
|
|
80
|
-
if (options.maxTurns !== void 0) args.push("--max-turns", String(options.maxTurns));
|
|
81
|
-
if (options.maxContextTokens !== void 0) args.push("--max-context-tokens", String(options.maxContextTokens));
|
|
82
|
-
if (options.additionalFlags && options.additionalFlags.length > 0) args.push(...options.additionalFlags);
|
|
83
|
-
const envEntries = {};
|
|
84
|
-
const unsetKeys = ["CLAUDECODE", "CLAUDE_CODE_ENTRYPOINT"];
|
|
85
|
-
if (options.billingMode === "api" && options.apiKey) envEntries.ANTHROPIC_API_KEY = options.apiKey;
|
|
86
|
-
else unsetKeys.push("ANTHROPIC_API_KEY");
|
|
87
|
-
if (options.otlpEndpoint !== void 0) {
|
|
88
|
-
envEntries.CLAUDE_CODE_ENABLE_TELEMETRY = "1";
|
|
89
|
-
envEntries.OTEL_LOGS_EXPORTER = "otlp";
|
|
90
|
-
envEntries.OTEL_METRICS_EXPORTER = "otlp";
|
|
91
|
-
envEntries.OTEL_EXPORTER_OTLP_PROTOCOL = "http/json";
|
|
92
|
-
envEntries.OTEL_EXPORTER_OTLP_ENDPOINT = options.otlpEndpoint;
|
|
93
|
-
envEntries.OTEL_LOG_TOOL_DETAILS = "1";
|
|
94
|
-
envEntries.OTEL_METRIC_EXPORT_INTERVAL = "10000";
|
|
95
|
-
envEntries.OTEL_EXPORTER_OTLP_TIMEOUT = "5000";
|
|
96
|
-
const resourceAttrs = [];
|
|
97
|
-
if (options.storyKey !== void 0) resourceAttrs.push(`substrate.story_key=${options.storyKey}`);
|
|
98
|
-
if (options.taskType !== void 0) resourceAttrs.push(`substrate.task_type=${options.taskType}`);
|
|
99
|
-
if (options.dispatchId !== void 0) resourceAttrs.push(`substrate.dispatch_id=${options.dispatchId}`);
|
|
100
|
-
if (resourceAttrs.length > 0) envEntries.OTEL_RESOURCE_ATTRIBUTES = resourceAttrs.join(",");
|
|
101
|
-
}
|
|
102
|
-
return {
|
|
103
|
-
binary: "claude",
|
|
104
|
-
args,
|
|
105
|
-
env: envEntries,
|
|
106
|
-
unsetEnvKeys: unsetKeys,
|
|
107
|
-
cwd: options.worktreePath
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Build spawn command for plan generation.
|
|
112
|
-
* Appends a structured planning directive to the prompt.
|
|
113
|
-
*/
|
|
114
|
-
buildPlanningCommand(request, options) {
|
|
115
|
-
const model = options.model ?? DEFAULT_MODEL$1;
|
|
116
|
-
const planningPrompt = this._buildPlanningPrompt(request);
|
|
117
|
-
const args = [
|
|
118
|
-
"-p",
|
|
119
|
-
"--model",
|
|
120
|
-
model
|
|
121
|
-
];
|
|
122
|
-
if (options.additionalFlags && options.additionalFlags.length > 0) args.push(...options.additionalFlags);
|
|
123
|
-
const envEntries = {};
|
|
124
|
-
const planUnsetKeys = ["CLAUDECODE", "CLAUDE_CODE_ENTRYPOINT"];
|
|
125
|
-
if (options.billingMode === "api" && options.apiKey) envEntries.ANTHROPIC_API_KEY = options.apiKey;
|
|
126
|
-
else planUnsetKeys.push("ANTHROPIC_API_KEY");
|
|
127
|
-
return {
|
|
128
|
-
binary: "claude",
|
|
129
|
-
args,
|
|
130
|
-
env: envEntries,
|
|
131
|
-
unsetEnvKeys: planUnsetKeys,
|
|
132
|
-
cwd: options.worktreePath
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Parse Claude CLI JSON stdout output into TaskResult.
|
|
137
|
-
*/
|
|
138
|
-
parseOutput(stdout, stderr, exitCode) {
|
|
139
|
-
if (exitCode !== 0) return {
|
|
140
|
-
success: false,
|
|
141
|
-
output: stdout,
|
|
142
|
-
error: stderr || `Process exited with code ${String(exitCode)}`,
|
|
143
|
-
exitCode
|
|
144
|
-
};
|
|
145
|
-
if (stdout.trim() === "") return {
|
|
146
|
-
success: true,
|
|
147
|
-
output: "",
|
|
148
|
-
exitCode
|
|
149
|
-
};
|
|
150
|
-
try {
|
|
151
|
-
const parsed = JSON.parse(stdout.trim());
|
|
152
|
-
const success = parsed.status === "completed" || parsed.status === void 0;
|
|
153
|
-
const rawTokens = parsed.metadata?.tokensUsed;
|
|
154
|
-
const tokensUsed = rawTokens !== void 0 ? {
|
|
155
|
-
input: rawTokens.input ?? 0,
|
|
156
|
-
output: rawTokens.output ?? 0,
|
|
157
|
-
total: (rawTokens.input ?? 0) + (rawTokens.output ?? 0)
|
|
158
|
-
} : void 0;
|
|
159
|
-
const executionTime = parsed.metadata?.executionTime;
|
|
160
|
-
return {
|
|
161
|
-
success: success && !parsed.error,
|
|
162
|
-
output: parsed.output ?? stdout,
|
|
163
|
-
...parsed.error ? { error: parsed.error } : {},
|
|
164
|
-
exitCode,
|
|
165
|
-
metadata: {
|
|
166
|
-
...executionTime !== void 0 ? { executionTime } : {},
|
|
167
|
-
...tokensUsed !== void 0 ? { tokensUsed } : {}
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
} catch {
|
|
171
|
-
return {
|
|
172
|
-
success: true,
|
|
173
|
-
output: stdout,
|
|
174
|
-
exitCode
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Parse plan generation output from Claude.
|
|
180
|
-
*/
|
|
181
|
-
parsePlanOutput(stdout, stderr, exitCode) {
|
|
182
|
-
if (exitCode !== 0) return {
|
|
183
|
-
success: false,
|
|
184
|
-
tasks: [],
|
|
185
|
-
error: stderr || `Process exited with code ${String(exitCode)}`,
|
|
186
|
-
rawOutput: stdout
|
|
187
|
-
};
|
|
188
|
-
if (stdout.trim() === "") return {
|
|
189
|
-
success: false,
|
|
190
|
-
tasks: [],
|
|
191
|
-
error: "Empty output from plan generation",
|
|
192
|
-
rawOutput: stdout
|
|
193
|
-
};
|
|
194
|
-
try {
|
|
195
|
-
const parsed = JSON.parse(stripCodeFences$2(stdout));
|
|
196
|
-
if (!Array.isArray(parsed.tasks)) return {
|
|
197
|
-
success: false,
|
|
198
|
-
tasks: [],
|
|
199
|
-
error: "Plan output missing tasks array",
|
|
200
|
-
rawOutput: stdout
|
|
201
|
-
};
|
|
202
|
-
const tasks = parsed.tasks.map((t) => ({
|
|
203
|
-
title: t.title ?? "Untitled task",
|
|
204
|
-
description: t.description ?? "",
|
|
205
|
-
...t.complexity !== void 0 ? { complexity: t.complexity } : {},
|
|
206
|
-
...t.dependencies !== void 0 ? { dependencies: t.dependencies } : {}
|
|
207
|
-
}));
|
|
208
|
-
return {
|
|
209
|
-
success: true,
|
|
210
|
-
tasks,
|
|
211
|
-
rawOutput: stdout
|
|
212
|
-
};
|
|
213
|
-
} catch {
|
|
214
|
-
return {
|
|
215
|
-
success: false,
|
|
216
|
-
tasks: [],
|
|
217
|
-
error: "Failed to parse plan output as JSON",
|
|
218
|
-
rawOutput: stdout
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Estimate token count using character-based heuristic.
|
|
224
|
-
* Approximation: 1 token ≈ 3 characters for English text.
|
|
225
|
-
*/
|
|
226
|
-
estimateTokens(prompt) {
|
|
227
|
-
const input = Math.ceil(prompt.length / CHARS_PER_TOKEN$2);
|
|
228
|
-
const output = Math.ceil(input * OUTPUT_RATIO$2);
|
|
229
|
-
return {
|
|
230
|
-
input,
|
|
231
|
-
output,
|
|
232
|
-
total: input + output
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Return Claude Code's capabilities.
|
|
237
|
-
*/
|
|
238
|
-
getCapabilities() {
|
|
239
|
-
return {
|
|
240
|
-
supportsJsonOutput: true,
|
|
241
|
-
supportsStreaming: true,
|
|
242
|
-
supportsSubscriptionBilling: true,
|
|
243
|
-
supportsApiBilling: true,
|
|
244
|
-
supportsPlanGeneration: true,
|
|
245
|
-
maxContextTokens: 1e6,
|
|
246
|
-
supportedTaskTypes: [
|
|
247
|
-
"code",
|
|
248
|
-
"refactor",
|
|
249
|
-
"test",
|
|
250
|
-
"review",
|
|
251
|
-
"debug",
|
|
252
|
-
"document",
|
|
253
|
-
"analyze"
|
|
254
|
-
],
|
|
255
|
-
supportedLanguages: ["*"]
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
_detectBillingModes(_versionOutput) {
|
|
259
|
-
const explicit = process.env.ADT_BILLING_MODE;
|
|
260
|
-
if (explicit === "subscription" || explicit === "api" || explicit === "free") return [explicit];
|
|
261
|
-
const modes = ["subscription"];
|
|
262
|
-
if (process.env.ANTHROPIC_API_KEY) modes.push("api");
|
|
263
|
-
return modes;
|
|
264
|
-
}
|
|
265
|
-
_buildPlanningPrompt(request) {
|
|
266
|
-
const maxTasks = request.maxTasks ?? 10;
|
|
267
|
-
const contextSection = request.context ? `\n\nAdditional context:\n${request.context}` : "";
|
|
268
|
-
return `Generate a detailed task plan for the following goal:\n${request.goal}${contextSection}\n\nOutput a JSON object with a "tasks" array. Each task should have: "title" (string), "description" (string), "complexity" (1-10 integer), "dependencies" (array of task titles this depends on). Produce at most ${String(maxTasks)} tasks. Output ONLY raw valid JSON — no markdown, no code fences, no explanation. Start your response with { and end with }.`;
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
//#endregion
|
|
273
|
-
//#region src/adapters/codex-adapter.ts
|
|
274
|
-
const execAsync$1 = promisify(exec);
|
|
275
|
-
/** Approximate characters per token for estimation */
|
|
276
|
-
const CHARS_PER_TOKEN$1 = 3;
|
|
277
|
-
/** Estimated output token multiplier relative to input */
|
|
278
|
-
const OUTPUT_RATIO$1 = .5;
|
|
279
|
-
/** Strip markdown code fences from LLM output (e.g. ```json ... ```) */
|
|
280
|
-
function stripCodeFences$1(raw) {
|
|
281
|
-
return raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```\s*$/, "").trim();
|
|
282
|
-
}
|
|
283
|
-
/** Codex default billing modes — subscription via `codex login`, or API key */
|
|
284
|
-
const CODEX_BILLING_MODES = ["subscription", "api"];
|
|
285
|
-
/**
|
|
286
|
-
* Adapter for the OpenAI Codex CLI agent.
|
|
287
|
-
*
|
|
288
|
-
* Codex CLI uses stdin for the prompt and outputs JSON when --json flag is used.
|
|
289
|
-
* Codex supports subscription billing (via `codex login`) and API key billing.
|
|
290
|
-
*/
|
|
291
|
-
var CodexCLIAdapter = class {
|
|
292
|
-
id = "codex";
|
|
293
|
-
displayName = "Codex CLI";
|
|
294
|
-
adapterVersion = "1.0.0";
|
|
295
|
-
/**
|
|
296
|
-
* Verify the `codex` binary is installed and responsive.
|
|
297
|
-
*/
|
|
298
|
-
async healthCheck() {
|
|
299
|
-
try {
|
|
300
|
-
const { stdout } = await execAsync$1("codex --version", { timeout: 1e4 });
|
|
301
|
-
const output = stdout.trim();
|
|
302
|
-
let cliPath;
|
|
303
|
-
try {
|
|
304
|
-
const whichResult = await execAsync$1("which codex", { timeout: 5e3 });
|
|
305
|
-
cliPath = whichResult.stdout.trim();
|
|
306
|
-
} catch {}
|
|
307
|
-
return {
|
|
308
|
-
healthy: true,
|
|
309
|
-
version: output,
|
|
310
|
-
...cliPath !== void 0 ? { cliPath } : {},
|
|
311
|
-
detectedBillingModes: CODEX_BILLING_MODES,
|
|
312
|
-
supportsHeadless: true
|
|
313
|
-
};
|
|
314
|
-
} catch (err) {
|
|
315
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
316
|
-
return {
|
|
317
|
-
healthy: false,
|
|
318
|
-
error: `Codex CLI not available: ${message}`,
|
|
319
|
-
supportsHeadless: false
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
/**
|
|
324
|
-
* Build spawn command for a coding task.
|
|
325
|
-
* Uses: `codex exec --json` with prompt delivered via stdin.
|
|
326
|
-
*/
|
|
327
|
-
buildCommand(prompt, options) {
|
|
328
|
-
const args = ["exec", "--json"];
|
|
329
|
-
if (options.additionalFlags && options.additionalFlags.length > 0) args.push(...options.additionalFlags);
|
|
330
|
-
const envEntries = {};
|
|
331
|
-
if (options.apiKey) envEntries.OPENAI_API_KEY = options.apiKey;
|
|
332
|
-
const hasEnv = Object.keys(envEntries).length > 0;
|
|
333
|
-
return {
|
|
334
|
-
binary: "codex",
|
|
335
|
-
args,
|
|
336
|
-
...hasEnv ? { env: envEntries } : {},
|
|
337
|
-
cwd: options.worktreePath,
|
|
338
|
-
stdin: prompt
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
/**
|
|
342
|
-
* Build spawn command for plan generation.
|
|
343
|
-
* Uses codex exec with a JSON plan generation prompt via stdin.
|
|
344
|
-
*/
|
|
345
|
-
buildPlanningCommand(request, options) {
|
|
346
|
-
const planningPrompt = this._buildPlanningPrompt(request);
|
|
347
|
-
const args = [
|
|
348
|
-
"exec",
|
|
349
|
-
planningPrompt,
|
|
350
|
-
"--sandbox",
|
|
351
|
-
"read-only"
|
|
352
|
-
];
|
|
353
|
-
if (options.additionalFlags && options.additionalFlags.length > 0) args.push(...options.additionalFlags);
|
|
354
|
-
const envEntries = {};
|
|
355
|
-
if (options.apiKey) envEntries.OPENAI_API_KEY = options.apiKey;
|
|
356
|
-
const hasEnv = Object.keys(envEntries).length > 0;
|
|
357
|
-
return {
|
|
358
|
-
binary: "codex",
|
|
359
|
-
args,
|
|
360
|
-
...hasEnv ? { env: envEntries } : {},
|
|
361
|
-
cwd: options.worktreePath
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
/**
|
|
365
|
-
* Parse Codex CLI JSON output into a TaskResult.
|
|
366
|
-
*/
|
|
367
|
-
parseOutput(stdout, stderr, exitCode) {
|
|
368
|
-
if (exitCode !== 0) return {
|
|
369
|
-
success: false,
|
|
370
|
-
output: stdout,
|
|
371
|
-
error: stderr || `Process exited with code ${String(exitCode)}`,
|
|
372
|
-
exitCode
|
|
373
|
-
};
|
|
374
|
-
if (stdout.trim() === "") return {
|
|
375
|
-
success: true,
|
|
376
|
-
output: "",
|
|
377
|
-
exitCode
|
|
378
|
-
};
|
|
379
|
-
try {
|
|
380
|
-
const parsed = JSON.parse(stdout.trim());
|
|
381
|
-
const success = parsed.status === "success" || parsed.status === "completed" || parsed.status === void 0 && !parsed.error;
|
|
382
|
-
const inputTokens = parsed.tokens?.input ?? 0;
|
|
383
|
-
const outputTokens = parsed.tokens?.output ?? 0;
|
|
384
|
-
const totalTokens = parsed.tokens?.total ?? inputTokens + outputTokens;
|
|
385
|
-
const hasTokens = inputTokens > 0 || outputTokens > 0;
|
|
386
|
-
const tokensUsed = hasTokens ? {
|
|
387
|
-
input: inputTokens,
|
|
388
|
-
output: outputTokens,
|
|
389
|
-
total: totalTokens
|
|
390
|
-
} : void 0;
|
|
391
|
-
const executionTime = parsed.executionTime;
|
|
392
|
-
return {
|
|
393
|
-
success: success && !parsed.error,
|
|
394
|
-
output: parsed.output ?? parsed.result ?? stdout,
|
|
395
|
-
...parsed.error ? { error: parsed.error } : {},
|
|
396
|
-
exitCode,
|
|
397
|
-
metadata: {
|
|
398
|
-
...executionTime !== void 0 ? { executionTime } : {},
|
|
399
|
-
...tokensUsed !== void 0 ? { tokensUsed } : {}
|
|
400
|
-
}
|
|
401
|
-
};
|
|
402
|
-
} catch {
|
|
403
|
-
return {
|
|
404
|
-
success: true,
|
|
405
|
-
output: stdout,
|
|
406
|
-
exitCode
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
/**
|
|
411
|
-
* Parse Codex plan generation output.
|
|
412
|
-
*/
|
|
413
|
-
parsePlanOutput(stdout, stderr, exitCode) {
|
|
414
|
-
if (exitCode !== 0) return {
|
|
415
|
-
success: false,
|
|
416
|
-
tasks: [],
|
|
417
|
-
error: stderr || `Process exited with code ${String(exitCode)}`,
|
|
418
|
-
rawOutput: stdout
|
|
419
|
-
};
|
|
420
|
-
if (stdout.trim() === "") return {
|
|
421
|
-
success: false,
|
|
422
|
-
tasks: [],
|
|
423
|
-
error: "Empty output from plan generation",
|
|
424
|
-
rawOutput: stdout
|
|
425
|
-
};
|
|
426
|
-
try {
|
|
427
|
-
const parsed = JSON.parse(stripCodeFences$1(stdout));
|
|
428
|
-
if (parsed.tasks === void 0 && parsed.plan === void 0) return {
|
|
429
|
-
success: false,
|
|
430
|
-
tasks: [],
|
|
431
|
-
error: "Plan output missing tasks array",
|
|
432
|
-
rawOutput: stdout
|
|
433
|
-
};
|
|
434
|
-
const rawTasks = parsed.tasks ?? parsed.plan ?? [];
|
|
435
|
-
if (!Array.isArray(rawTasks)) return {
|
|
436
|
-
success: false,
|
|
437
|
-
tasks: [],
|
|
438
|
-
error: "Plan output missing tasks array",
|
|
439
|
-
rawOutput: stdout
|
|
440
|
-
};
|
|
441
|
-
const tasks = rawTasks.map((t) => {
|
|
442
|
-
const deps = "dependencies" in t ? t.dependencies : t.deps;
|
|
443
|
-
return {
|
|
444
|
-
title: t.title ?? "Untitled task",
|
|
445
|
-
description: t.description ?? "",
|
|
446
|
-
...t.complexity !== void 0 ? { complexity: t.complexity } : {},
|
|
447
|
-
...deps !== void 0 ? { dependencies: deps } : {}
|
|
448
|
-
};
|
|
449
|
-
});
|
|
450
|
-
return {
|
|
451
|
-
success: true,
|
|
452
|
-
tasks,
|
|
453
|
-
rawOutput: stdout
|
|
454
|
-
};
|
|
455
|
-
} catch {
|
|
456
|
-
return {
|
|
457
|
-
success: false,
|
|
458
|
-
tasks: [],
|
|
459
|
-
error: "Failed to parse Codex plan output as JSON",
|
|
460
|
-
rawOutput: stdout
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
/**
|
|
465
|
-
* Estimate token count using character-based heuristic.
|
|
466
|
-
*/
|
|
467
|
-
estimateTokens(prompt) {
|
|
468
|
-
const input = Math.ceil(prompt.length / CHARS_PER_TOKEN$1);
|
|
469
|
-
const output = Math.ceil(input * OUTPUT_RATIO$1);
|
|
470
|
-
return {
|
|
471
|
-
input,
|
|
472
|
-
output,
|
|
473
|
-
total: input + output
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
/**
|
|
477
|
-
* Return Codex CLI capabilities.
|
|
478
|
-
*/
|
|
479
|
-
getCapabilities() {
|
|
480
|
-
return {
|
|
481
|
-
supportsJsonOutput: true,
|
|
482
|
-
supportsStreaming: false,
|
|
483
|
-
supportsSubscriptionBilling: true,
|
|
484
|
-
supportsApiBilling: true,
|
|
485
|
-
supportsPlanGeneration: true,
|
|
486
|
-
maxContextTokens: 128e3,
|
|
487
|
-
supportedTaskTypes: [
|
|
488
|
-
"code",
|
|
489
|
-
"refactor",
|
|
490
|
-
"test",
|
|
491
|
-
"debug",
|
|
492
|
-
"analyze"
|
|
493
|
-
],
|
|
494
|
-
supportedLanguages: ["*"]
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
_buildPlanningPrompt(request) {
|
|
498
|
-
const maxTasks = request.maxTasks ?? 10;
|
|
499
|
-
const contextSection = request.context ? `\n\nAdditional context:\n${request.context}` : "";
|
|
500
|
-
return `Generate a detailed task plan for the following goal:\n${request.goal}${contextSection}\n\nOutput a JSON object with a "tasks" array. Each task should have: "title" (string), "description" (string), "complexity" (1-10 integer), "dependencies" (array of task titles this depends on). Produce at most ${String(maxTasks)} tasks. Output ONLY raw valid JSON — no markdown, no code fences, no explanation. Start your response with { and end with }.`;
|
|
501
|
-
}
|
|
502
|
-
};
|
|
503
|
-
|
|
504
|
-
//#endregion
|
|
505
|
-
//#region src/adapters/gemini-adapter.ts
|
|
506
|
-
const execAsync = promisify(exec);
|
|
507
|
-
/** Default model used when none is specified */
|
|
508
|
-
const DEFAULT_MODEL = "gemini-2.0-flash";
|
|
509
|
-
/** Approximate characters per token for estimation */
|
|
510
|
-
const CHARS_PER_TOKEN = 3;
|
|
511
|
-
/** Estimated output token multiplier relative to input */
|
|
512
|
-
const OUTPUT_RATIO = .5;
|
|
513
|
-
/**
|
|
514
|
-
* Strip markdown code fences from LLM output.
|
|
515
|
-
* LLMs often wrap JSON in ```json ... ``` despite being told not to.
|
|
516
|
-
*/
|
|
517
|
-
function stripCodeFences(raw) {
|
|
518
|
-
const stripped = raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```\s*$/, "");
|
|
519
|
-
return stripped.trim();
|
|
520
|
-
}
|
|
521
|
-
/**
|
|
522
|
-
* Adapter for the Google Gemini CLI agent.
|
|
523
|
-
*
|
|
524
|
-
* Gemini CLI follows similar patterns to Claude Code: prompt via `-p` flag,
|
|
525
|
-
* JSON output via `--output-format json`, and model via `--model`.
|
|
526
|
-
*/
|
|
527
|
-
var GeminiCLIAdapter = class {
|
|
528
|
-
id = "gemini";
|
|
529
|
-
displayName = "Gemini CLI";
|
|
530
|
-
adapterVersion = "1.0.0";
|
|
531
|
-
/**
|
|
532
|
-
* Verify the `gemini` binary is installed and responsive.
|
|
533
|
-
* Detects subscription vs API billing mode.
|
|
534
|
-
*/
|
|
535
|
-
async healthCheck() {
|
|
536
|
-
try {
|
|
537
|
-
const { stdout } = await execAsync("gemini --version", { timeout: 1e4 });
|
|
538
|
-
const output = stdout.trim();
|
|
539
|
-
const detectedBillingModes = this._detectBillingModes(output);
|
|
540
|
-
let cliPath;
|
|
541
|
-
try {
|
|
542
|
-
const whichResult = await execAsync("which gemini", { timeout: 5e3 });
|
|
543
|
-
cliPath = whichResult.stdout.trim();
|
|
544
|
-
} catch {}
|
|
545
|
-
return {
|
|
546
|
-
healthy: true,
|
|
547
|
-
version: output,
|
|
548
|
-
...cliPath !== void 0 ? { cliPath } : {},
|
|
549
|
-
detectedBillingModes,
|
|
550
|
-
supportsHeadless: true
|
|
551
|
-
};
|
|
552
|
-
} catch (err) {
|
|
553
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
554
|
-
return {
|
|
555
|
-
healthy: false,
|
|
556
|
-
error: `Gemini CLI not available: ${message}`,
|
|
557
|
-
supportsHeadless: false
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
/**
|
|
562
|
-
* Build spawn command for a coding task.
|
|
563
|
-
* Uses: `gemini -p <prompt> --output-format json --model <model>`
|
|
564
|
-
*/
|
|
565
|
-
buildCommand(prompt, options) {
|
|
566
|
-
const model = options.model ?? DEFAULT_MODEL;
|
|
567
|
-
const args = [
|
|
568
|
-
"-p",
|
|
569
|
-
prompt,
|
|
570
|
-
"--output-format",
|
|
571
|
-
"json",
|
|
572
|
-
"--model",
|
|
573
|
-
model
|
|
574
|
-
];
|
|
575
|
-
if (options.additionalFlags && options.additionalFlags.length > 0) args.push(...options.additionalFlags);
|
|
576
|
-
const envEntries = {};
|
|
577
|
-
if (options.billingMode === "api" && options.apiKey) envEntries.GEMINI_API_KEY = options.apiKey;
|
|
578
|
-
const hasEnv = Object.keys(envEntries).length > 0;
|
|
579
|
-
return {
|
|
580
|
-
binary: "gemini",
|
|
581
|
-
args,
|
|
582
|
-
...hasEnv ? { env: envEntries } : {},
|
|
583
|
-
cwd: options.worktreePath
|
|
584
|
-
};
|
|
585
|
-
}
|
|
586
|
-
/**
|
|
587
|
-
* Build spawn command for plan generation.
|
|
588
|
-
*/
|
|
589
|
-
buildPlanningCommand(request, options) {
|
|
590
|
-
const model = options.model ?? DEFAULT_MODEL;
|
|
591
|
-
const planningPrompt = this._buildPlanningPrompt(request);
|
|
592
|
-
const args = [
|
|
593
|
-
planningPrompt,
|
|
594
|
-
"--model",
|
|
595
|
-
model
|
|
596
|
-
];
|
|
597
|
-
if (options.additionalFlags && options.additionalFlags.length > 0) args.push(...options.additionalFlags);
|
|
598
|
-
const envEntries = {};
|
|
599
|
-
if (options.billingMode === "api" && options.apiKey) envEntries.GEMINI_API_KEY = options.apiKey;
|
|
600
|
-
const hasEnv = Object.keys(envEntries).length > 0;
|
|
601
|
-
return {
|
|
602
|
-
binary: "gemini",
|
|
603
|
-
args,
|
|
604
|
-
...hasEnv ? { env: envEntries } : {},
|
|
605
|
-
cwd: options.worktreePath
|
|
606
|
-
};
|
|
607
|
-
}
|
|
608
|
-
/**
|
|
609
|
-
* Parse Gemini CLI JSON output into a TaskResult.
|
|
610
|
-
*/
|
|
611
|
-
parseOutput(stdout, stderr, exitCode) {
|
|
612
|
-
if (exitCode !== 0) return {
|
|
613
|
-
success: false,
|
|
614
|
-
output: stdout,
|
|
615
|
-
error: stderr || `Process exited with code ${String(exitCode)}`,
|
|
616
|
-
exitCode
|
|
617
|
-
};
|
|
618
|
-
if (stdout.trim() === "") return {
|
|
619
|
-
success: true,
|
|
620
|
-
output: "",
|
|
621
|
-
exitCode
|
|
622
|
-
};
|
|
623
|
-
try {
|
|
624
|
-
const parsed = JSON.parse(stdout.trim());
|
|
625
|
-
const success = parsed.status === "completed" || parsed.status === void 0;
|
|
626
|
-
const usageMeta = parsed.metadata?.usageMetadata;
|
|
627
|
-
const inputTokens = parsed.metadata?.tokensUsed?.input ?? usageMeta?.promptTokenCount ?? 0;
|
|
628
|
-
const outputTokens = parsed.metadata?.tokensUsed?.output ?? usageMeta?.candidatesTokenCount ?? 0;
|
|
629
|
-
const hasTokens = inputTokens > 0 || outputTokens > 0;
|
|
630
|
-
const tokensUsed = hasTokens ? {
|
|
631
|
-
input: inputTokens,
|
|
632
|
-
output: outputTokens,
|
|
633
|
-
total: inputTokens + outputTokens
|
|
634
|
-
} : void 0;
|
|
635
|
-
const executionTime = parsed.metadata?.executionTime;
|
|
636
|
-
return {
|
|
637
|
-
success: success && !parsed.error,
|
|
638
|
-
output: parsed.output ?? parsed.response ?? stdout,
|
|
639
|
-
...parsed.error ? { error: parsed.error } : {},
|
|
640
|
-
exitCode,
|
|
641
|
-
metadata: {
|
|
642
|
-
...executionTime !== void 0 ? { executionTime } : {},
|
|
643
|
-
...tokensUsed !== void 0 ? { tokensUsed } : {}
|
|
644
|
-
}
|
|
645
|
-
};
|
|
646
|
-
} catch {
|
|
647
|
-
return {
|
|
648
|
-
success: true,
|
|
649
|
-
output: stdout,
|
|
650
|
-
exitCode
|
|
651
|
-
};
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
/**
|
|
655
|
-
* Parse Gemini plan generation output.
|
|
656
|
-
*/
|
|
657
|
-
parsePlanOutput(stdout, stderr, exitCode) {
|
|
658
|
-
if (exitCode !== 0) return {
|
|
659
|
-
success: false,
|
|
660
|
-
tasks: [],
|
|
661
|
-
error: stderr || `Process exited with code ${String(exitCode)}`,
|
|
662
|
-
rawOutput: stdout
|
|
663
|
-
};
|
|
664
|
-
if (stdout.trim() === "") return {
|
|
665
|
-
success: false,
|
|
666
|
-
tasks: [],
|
|
667
|
-
error: "Empty output from plan generation",
|
|
668
|
-
rawOutput: stdout
|
|
669
|
-
};
|
|
670
|
-
try {
|
|
671
|
-
const parsed = JSON.parse(stripCodeFences(stdout));
|
|
672
|
-
if (!Array.isArray(parsed.tasks)) return {
|
|
673
|
-
success: false,
|
|
674
|
-
tasks: [],
|
|
675
|
-
error: "Plan output missing tasks array",
|
|
676
|
-
rawOutput: stdout
|
|
677
|
-
};
|
|
678
|
-
const tasks = parsed.tasks.map((t) => ({
|
|
679
|
-
title: t.title ?? "Untitled task",
|
|
680
|
-
description: t.description ?? "",
|
|
681
|
-
...t.complexity !== void 0 ? { complexity: t.complexity } : {},
|
|
682
|
-
...t.dependencies !== void 0 ? { dependencies: t.dependencies } : {}
|
|
683
|
-
}));
|
|
684
|
-
return {
|
|
685
|
-
success: true,
|
|
686
|
-
tasks,
|
|
687
|
-
rawOutput: stdout
|
|
688
|
-
};
|
|
689
|
-
} catch {
|
|
690
|
-
return {
|
|
691
|
-
success: false,
|
|
692
|
-
tasks: [],
|
|
693
|
-
error: "Failed to parse Gemini plan output as JSON",
|
|
694
|
-
rawOutput: stdout
|
|
695
|
-
};
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
/**
|
|
699
|
-
* Estimate token count using character-based heuristic.
|
|
700
|
-
*/
|
|
701
|
-
estimateTokens(prompt) {
|
|
702
|
-
const input = Math.ceil(prompt.length / CHARS_PER_TOKEN);
|
|
703
|
-
const output = Math.ceil(input * OUTPUT_RATIO);
|
|
704
|
-
return {
|
|
705
|
-
input,
|
|
706
|
-
output,
|
|
707
|
-
total: input + output
|
|
708
|
-
};
|
|
709
|
-
}
|
|
710
|
-
/**
|
|
711
|
-
* Return Gemini CLI capabilities.
|
|
712
|
-
*/
|
|
713
|
-
getCapabilities() {
|
|
714
|
-
return {
|
|
715
|
-
supportsJsonOutput: true,
|
|
716
|
-
supportsStreaming: true,
|
|
717
|
-
supportsSubscriptionBilling: true,
|
|
718
|
-
supportsApiBilling: true,
|
|
719
|
-
supportsPlanGeneration: true,
|
|
720
|
-
maxContextTokens: 1e6,
|
|
721
|
-
supportedTaskTypes: [
|
|
722
|
-
"code",
|
|
723
|
-
"refactor",
|
|
724
|
-
"test",
|
|
725
|
-
"review",
|
|
726
|
-
"debug",
|
|
727
|
-
"document",
|
|
728
|
-
"analyze"
|
|
729
|
-
],
|
|
730
|
-
supportedLanguages: ["*"]
|
|
731
|
-
};
|
|
732
|
-
}
|
|
733
|
-
_detectBillingModes(versionOutput) {
|
|
734
|
-
const explicit = process.env.ADT_BILLING_MODE;
|
|
735
|
-
if (explicit === "subscription" || explicit === "api" || explicit === "free") return [explicit];
|
|
736
|
-
const modes = [];
|
|
737
|
-
if (process.env.GEMINI_API_KEY) modes.push("api");
|
|
738
|
-
if (versionOutput.toLowerCase().includes("subscription")) modes.push("subscription");
|
|
739
|
-
if (modes.length === 0) return ["subscription", "api"];
|
|
740
|
-
return modes;
|
|
741
|
-
}
|
|
742
|
-
_buildPlanningPrompt(request) {
|
|
743
|
-
const maxTasks = request.maxTasks ?? 10;
|
|
744
|
-
const contextSection = request.context ? `\n\nAdditional context:\n${request.context}` : "";
|
|
745
|
-
return `Generate a detailed task plan for the following goal:\n${request.goal}${contextSection}\n\nOutput a JSON object with a "tasks" array. Each task should have: "title" (string), "description" (string), "complexity" (1-10 integer), "dependencies" (array of task titles this depends on). Produce at most ${String(maxTasks)} tasks. Output ONLY raw valid JSON — no markdown, no code fences, no explanation. Start your response with { and end with }.`;
|
|
746
|
-
}
|
|
747
|
-
};
|
|
748
|
-
|
|
749
|
-
//#endregion
|
|
750
|
-
//#region src/adapters/adapter-registry.ts
|
|
751
|
-
/**
|
|
752
|
-
* AdapterRegistry manages the lifecycle of WorkerAdapter instances.
|
|
753
|
-
*
|
|
754
|
-
* Usage:
|
|
755
|
-
* ```typescript
|
|
756
|
-
* const registry = new AdapterRegistry()
|
|
757
|
-
* const report = await registry.discoverAndRegister()
|
|
758
|
-
* const claude = registry.get('claude-code')
|
|
759
|
-
* ```
|
|
760
|
-
*/
|
|
761
|
-
var AdapterRegistry = class {
|
|
762
|
-
_adapters = new Map();
|
|
763
|
-
/**
|
|
764
|
-
* Register an adapter by its id.
|
|
765
|
-
* Overwrites any existing adapter with the same id.
|
|
766
|
-
*/
|
|
767
|
-
register(adapter) {
|
|
768
|
-
this._adapters.set(adapter.id, adapter);
|
|
769
|
-
}
|
|
770
|
-
/**
|
|
771
|
-
* Retrieve a registered adapter by id.
|
|
772
|
-
* @returns The adapter, or undefined if not registered
|
|
773
|
-
*/
|
|
774
|
-
get(id) {
|
|
775
|
-
return this._adapters.get(id);
|
|
776
|
-
}
|
|
777
|
-
/**
|
|
778
|
-
* Return all registered adapters as an array.
|
|
779
|
-
*/
|
|
780
|
-
getAll() {
|
|
781
|
-
return Array.from(this._adapters.values());
|
|
782
|
-
}
|
|
783
|
-
/**
|
|
784
|
-
* Return all registered adapters that support plan generation.
|
|
785
|
-
*/
|
|
786
|
-
getPlanningCapable() {
|
|
787
|
-
return this.getAll().filter((adapter) => adapter.getCapabilities().supportsPlanGeneration);
|
|
788
|
-
}
|
|
789
|
-
/**
|
|
790
|
-
* Instantiate all built-in adapters, run health checks sequentially,
|
|
791
|
-
* and register those that pass.
|
|
792
|
-
*
|
|
793
|
-
* Failed adapters are included in the report but do NOT prevent startup.
|
|
794
|
-
*
|
|
795
|
-
* @returns Discovery report with per-adapter results
|
|
796
|
-
*/
|
|
797
|
-
async discoverAndRegister() {
|
|
798
|
-
const builtInAdapters = [
|
|
799
|
-
new ClaudeCodeAdapter(),
|
|
800
|
-
new CodexCLIAdapter(),
|
|
801
|
-
new GeminiCLIAdapter()
|
|
802
|
-
];
|
|
803
|
-
const results = [];
|
|
804
|
-
let registeredCount = 0;
|
|
805
|
-
let failedCount = 0;
|
|
806
|
-
for (const adapter of builtInAdapters) {
|
|
807
|
-
let healthResult;
|
|
808
|
-
try {
|
|
809
|
-
healthResult = await adapter.healthCheck();
|
|
810
|
-
} catch (err) {
|
|
811
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
812
|
-
healthResult = {
|
|
813
|
-
healthy: false,
|
|
814
|
-
error: `Unexpected error during health check: ${message}`,
|
|
815
|
-
supportsHeadless: false
|
|
816
|
-
};
|
|
817
|
-
}
|
|
818
|
-
const registered = healthResult.healthy;
|
|
819
|
-
if (registered) {
|
|
820
|
-
this.register(adapter);
|
|
821
|
-
registeredCount++;
|
|
822
|
-
} else failedCount++;
|
|
823
|
-
results.push({
|
|
824
|
-
adapterId: adapter.id,
|
|
825
|
-
displayName: adapter.displayName,
|
|
826
|
-
healthResult,
|
|
827
|
-
registered
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
|
-
return {
|
|
831
|
-
registeredCount,
|
|
832
|
-
failedCount,
|
|
833
|
-
results
|
|
834
|
-
};
|
|
835
|
-
}
|
|
836
|
-
};
|
|
837
|
-
|
|
838
|
-
//#endregion
|
|
839
|
-
export { AdapterRegistry, ClaudeCodeAdapter, CodexCLIAdapter, GeminiCLIAdapter };
|
|
840
|
-
//# sourceMappingURL=adapter-registry-D2zdMwVu.js.map
|