agent-worker 0.14.0 → 0.15.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/README.md +6 -3
- package/dist/{backends-Cv0oM9Ru.mjs → backends-BYWmuyF9.mjs} +1 -1
- package/dist/{backends-C6WBIn9H.mjs → backends-C7pQwuAx.mjs} +265 -227
- package/dist/cli/index.mjs +648 -393
- package/dist/context-CdcZpO-0.mjs +4 -0
- package/dist/create-tool-gcUuI1FD.mjs +32 -0
- package/dist/index.d.mts +4 -33
- package/dist/index.mjs +21 -20
- package/dist/{memory-provider-0nuDxzYQ.mjs → memory-provider-ZLOKyCxA.mjs} +8 -3
- package/dist/{runner-DV86expc.mjs → runner-DB-b57iZ.mjs} +53 -46
- package/dist/workflow-DQ6Eju4n.mjs +664 -0
- package/package.json +3 -3
- package/dist/context-CzqQeThq.mjs +0 -4
- package/dist/workflow-DogkVjOs.mjs +0 -301
- /package/dist/{display-pretty-BCJq5v9d.mjs → display-pretty-Kyd40DEF.mjs} +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { gateway, generateText } from "ai";
|
|
2
2
|
import { execa } from "execa";
|
|
3
|
-
import { existsSync
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { stringify } from "yaml";
|
|
6
5
|
|
|
7
6
|
//#region src/agent/models.ts
|
|
8
7
|
const providerCache = {};
|
|
@@ -119,12 +118,21 @@ async function createModelWithProvider(modelName, provider) {
|
|
|
119
118
|
* Examples: anthropic:claude-sonnet-4-5, openai:gpt-5.2, deepseek:deepseek-chat
|
|
120
119
|
*/
|
|
121
120
|
function createModel(modelId) {
|
|
122
|
-
if (modelId.includes("/"))
|
|
121
|
+
if (modelId.includes("/")) {
|
|
122
|
+
if (process.env.AI_GATEWAY_API_KEY) return gateway(modelId);
|
|
123
|
+
const slashIndex = modelId.indexOf("/");
|
|
124
|
+
const provider = modelId.slice(0, slashIndex);
|
|
125
|
+
const modelName = modelId.slice(slashIndex + 1);
|
|
126
|
+
if (provider in providerCache && providerCache[provider]) return providerCache[provider](modelName);
|
|
127
|
+
throw new Error(`Provider '${provider}' not loaded. Call createModelAsync() first or set AI_GATEWAY_API_KEY for gateway access.`);
|
|
128
|
+
}
|
|
123
129
|
if (!modelId.includes(":")) {
|
|
124
130
|
const provider = modelId;
|
|
125
131
|
if (provider in FRONTIER_MODELS) {
|
|
126
132
|
const defaultModel = FRONTIER_MODELS[provider][0];
|
|
127
|
-
return gateway(`${provider}/${defaultModel}`);
|
|
133
|
+
if (process.env.AI_GATEWAY_API_KEY) return gateway(`${provider}/${defaultModel}`);
|
|
134
|
+
if (provider in providerCache && providerCache[provider]) return providerCache[provider](defaultModel);
|
|
135
|
+
throw new Error(`Provider '${provider}' not loaded. Call createModelAsync() first or set AI_GATEWAY_API_KEY for gateway access.`);
|
|
128
136
|
}
|
|
129
137
|
throw new Error(`Unknown provider: ${modelId}. Supported: ${Object.keys(FRONTIER_MODELS).join(", ")}`);
|
|
130
138
|
}
|
|
@@ -133,19 +141,24 @@ function createModel(modelId) {
|
|
|
133
141
|
const modelName = modelId.slice(colonIndex + 1);
|
|
134
142
|
if (!modelName) throw new Error(`Invalid model identifier: ${modelId}. Model name is required.`);
|
|
135
143
|
if (provider in providerCache && providerCache[provider]) return providerCache[provider](modelName);
|
|
136
|
-
throw new Error(`Provider '${provider}' not loaded.
|
|
144
|
+
throw new Error(`Provider '${provider}' not loaded. Call createModelAsync() first or set AI_GATEWAY_API_KEY for gateway access.`);
|
|
137
145
|
}
|
|
138
146
|
/**
|
|
139
147
|
* Async version of createModel - supports lazy loading of direct providers
|
|
140
148
|
* Use this when you need direct provider access (provider:model format)
|
|
141
149
|
*/
|
|
142
150
|
async function createModelAsync(modelId) {
|
|
143
|
-
if (modelId.includes("/"))
|
|
151
|
+
if (modelId.includes("/")) {
|
|
152
|
+
if (process.env.AI_GATEWAY_API_KEY) return gateway(modelId);
|
|
153
|
+
const slashIndex = modelId.indexOf("/");
|
|
154
|
+
return loadProviderModel(modelId.slice(0, slashIndex), modelId.slice(slashIndex + 1));
|
|
155
|
+
}
|
|
144
156
|
if (!modelId.includes(":")) {
|
|
145
157
|
const provider = modelId;
|
|
146
158
|
if (provider in FRONTIER_MODELS) {
|
|
147
159
|
const defaultModel = FRONTIER_MODELS[provider][0];
|
|
148
|
-
return gateway(`${provider}/${defaultModel}`);
|
|
160
|
+
if (process.env.AI_GATEWAY_API_KEY) return gateway(`${provider}/${defaultModel}`);
|
|
161
|
+
return loadProviderModel(provider, defaultModel);
|
|
149
162
|
}
|
|
150
163
|
throw new Error(`Unknown provider: ${modelId}. Supported: ${Object.keys(FRONTIER_MODELS).join(", ")}`);
|
|
151
164
|
}
|
|
@@ -153,6 +166,13 @@ async function createModelAsync(modelId) {
|
|
|
153
166
|
const provider = modelId.slice(0, colonIndex);
|
|
154
167
|
const modelName = modelId.slice(colonIndex + 1);
|
|
155
168
|
if (!modelName) throw new Error(`Invalid model identifier: ${modelId}. Model name is required.`);
|
|
169
|
+
return loadProviderModel(provider, modelName);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Load a provider SDK and create a model instance.
|
|
173
|
+
* Used as fallback when AI Gateway is not available.
|
|
174
|
+
*/
|
|
175
|
+
async function loadProviderModel(provider, modelName) {
|
|
156
176
|
const config = PROVIDER_PACKAGES[provider];
|
|
157
177
|
if (!config) throw new Error(`Unknown provider: ${provider}. Supported: ${Object.keys(PROVIDER_PACKAGES).join(", ")}. Or use gateway format: provider/model (e.g., openai/gpt-5.2)`);
|
|
158
178
|
const providerFn = await loadProvider(provider, config.package, config.export);
|
|
@@ -210,6 +230,165 @@ const FRONTIER_MODELS = {
|
|
|
210
230
|
],
|
|
211
231
|
xai: ["grok-4", "grok-4-fast-reasoning"]
|
|
212
232
|
};
|
|
233
|
+
/**
|
|
234
|
+
* Environment variable that each provider uses for authentication.
|
|
235
|
+
* Ordered by priority — first match wins during auto-discovery.
|
|
236
|
+
*
|
|
237
|
+
* Gateway is first because it supports all providers via a single key.
|
|
238
|
+
*/
|
|
239
|
+
const PROVIDER_ENV_KEYS = {
|
|
240
|
+
gateway: "AI_GATEWAY_API_KEY",
|
|
241
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
242
|
+
openai: "OPENAI_API_KEY",
|
|
243
|
+
deepseek: "DEEPSEEK_API_KEY",
|
|
244
|
+
google: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
245
|
+
groq: "GROQ_API_KEY",
|
|
246
|
+
mistral: "MISTRAL_API_KEY",
|
|
247
|
+
xai: "XAI_API_KEY"
|
|
248
|
+
};
|
|
249
|
+
/** Provider discovery priority order */
|
|
250
|
+
const DISCOVERY_ORDER = [
|
|
251
|
+
"gateway",
|
|
252
|
+
"anthropic",
|
|
253
|
+
"openai",
|
|
254
|
+
"deepseek",
|
|
255
|
+
"google",
|
|
256
|
+
"groq",
|
|
257
|
+
"mistral",
|
|
258
|
+
"xai"
|
|
259
|
+
];
|
|
260
|
+
/**
|
|
261
|
+
* Reverse map: model name → provider name.
|
|
262
|
+
* Built from FRONTIER_MODELS so "deepseek-chat" → "deepseek", etc.
|
|
263
|
+
*/
|
|
264
|
+
const MODEL_TO_PROVIDER = {};
|
|
265
|
+
for (const [provider, models] of Object.entries(FRONTIER_MODELS)) for (const model of models) {
|
|
266
|
+
const shortName = model.includes("/") ? model.split("/").pop() : model;
|
|
267
|
+
MODEL_TO_PROVIDER[model] = provider;
|
|
268
|
+
if (shortName !== model) MODEL_TO_PROVIDER[shortName] = provider;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Discover the best available provider by scanning environment variables.
|
|
272
|
+
*
|
|
273
|
+
* Note: This function does NOT read AGENT_MODEL — that's handled by
|
|
274
|
+
* resolveModelFallback() which supports comma-separated fallback chains.
|
|
275
|
+
*
|
|
276
|
+
* @param options.preferredModel - If set, prefer the provider that owns this model.
|
|
277
|
+
* E.g. "deepseek-chat" → prefer "deepseek" if DEEPSEEK_API_KEY is set.
|
|
278
|
+
* @param options.env - Environment to scan (defaults to process.env).
|
|
279
|
+
* @returns The discovered provider and model, or null if none available.
|
|
280
|
+
*/
|
|
281
|
+
function discoverProvider(options) {
|
|
282
|
+
const env = options?.env ?? process.env;
|
|
283
|
+
const preferredModel = options?.preferredModel;
|
|
284
|
+
if (preferredModel && preferredModel !== "auto") {
|
|
285
|
+
const ownerProvider = MODEL_TO_PROVIDER[preferredModel];
|
|
286
|
+
if (ownerProvider) {
|
|
287
|
+
const envKey = PROVIDER_ENV_KEYS[ownerProvider];
|
|
288
|
+
if (envKey && env[envKey]) return {
|
|
289
|
+
provider: ownerProvider,
|
|
290
|
+
model: preferredModel.includes("/") ? preferredModel : `${ownerProvider}/${preferredModel}`
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
for (const provider of DISCOVERY_ORDER) {
|
|
295
|
+
if (!env[PROVIDER_ENV_KEYS[provider]]) continue;
|
|
296
|
+
if (provider === "gateway") {
|
|
297
|
+
if (preferredModel && preferredModel !== "auto") return {
|
|
298
|
+
provider: "gateway",
|
|
299
|
+
model: `${MODEL_TO_PROVIDER[preferredModel] || "anthropic"}/${preferredModel}`
|
|
300
|
+
};
|
|
301
|
+
return {
|
|
302
|
+
provider: "gateway",
|
|
303
|
+
model: getDefaultModel()
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
const defaultModel = FRONTIER_MODELS[provider]?.[0];
|
|
307
|
+
return {
|
|
308
|
+
provider,
|
|
309
|
+
model: defaultModel ? defaultModel.includes("/") ? defaultModel : `${provider}/${defaultModel}` : provider
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Check if a value is the "auto" sentinel.
|
|
316
|
+
*/
|
|
317
|
+
function isAutoProvider(value) {
|
|
318
|
+
return value === "auto";
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Check if a model's provider has a valid API key in the environment.
|
|
322
|
+
*/
|
|
323
|
+
function isModelAvailable(model, env) {
|
|
324
|
+
if (model === "auto") return true;
|
|
325
|
+
let provider;
|
|
326
|
+
provider = MODEL_TO_PROVIDER[model];
|
|
327
|
+
if (!provider && model.includes("/")) provider = model.split("/")[0];
|
|
328
|
+
if (!provider && model.includes(":")) provider = model.split(":")[0];
|
|
329
|
+
if (!provider) return false;
|
|
330
|
+
if (env[PROVIDER_ENV_KEYS["gateway"]]) return true;
|
|
331
|
+
const envKey = PROVIDER_ENV_KEYS[provider];
|
|
332
|
+
return !!envKey && !!env[envKey];
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Resolve a model to a single concrete value, supporting fallback chains.
|
|
336
|
+
*
|
|
337
|
+
* Resolution order:
|
|
338
|
+
* 1. AGENT_DEFAULT_MODELS env var — comma-separated preference list
|
|
339
|
+
* (e.g. "deepseek-chat, anthropic/claude-sonnet-4-5")
|
|
340
|
+
* 2. Workflow YAML model field (single string, or "auto")
|
|
341
|
+
* 3. Full auto-discovery — scan all provider API keys
|
|
342
|
+
*
|
|
343
|
+
* The preference list does NOT contain "auto" — the env var itself IS
|
|
344
|
+
* the auto configuration. After exhausting the explicit list, the system
|
|
345
|
+
* implicitly falls back to full provider discovery.
|
|
346
|
+
*
|
|
347
|
+
* Example:
|
|
348
|
+
* AGENT_DEFAULT_MODELS="deepseek-chat, anthropic/claude-sonnet-4-5"
|
|
349
|
+
* → try deepseek-chat (need DEEPSEEK_API_KEY)
|
|
350
|
+
* → try claude-sonnet-4-5 (need ANTHROPIC_API_KEY)
|
|
351
|
+
* → implicit fallback: discover any available provider
|
|
352
|
+
*
|
|
353
|
+
* @returns Resolved { model, provider } — never contains "auto".
|
|
354
|
+
* @throws if nothing is available (no explicit candidate and no provider key).
|
|
355
|
+
*/
|
|
356
|
+
function resolveModelFallback(config) {
|
|
357
|
+
const env = config.env ?? process.env;
|
|
358
|
+
const isProviderAuto = config.provider === "auto";
|
|
359
|
+
const autoModel = env.AGENT_DEFAULT_MODELS;
|
|
360
|
+
if (!isProviderAuto && config.model && config.model !== "auto" && !autoModel) return {
|
|
361
|
+
model: config.model,
|
|
362
|
+
provider: config.provider
|
|
363
|
+
};
|
|
364
|
+
const preferences = autoModel ? autoModel.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
365
|
+
for (const candidate of preferences) if (isModelAvailable(candidate, env)) return {
|
|
366
|
+
model: candidate,
|
|
367
|
+
provider: isProviderAuto ? void 0 : config.provider
|
|
368
|
+
};
|
|
369
|
+
if (isProviderAuto && config.model && config.model !== "auto") {
|
|
370
|
+
const model = config.model;
|
|
371
|
+
if (isModelAvailable(model, env)) {
|
|
372
|
+
const ownerProvider = MODEL_TO_PROVIDER[model];
|
|
373
|
+
if (ownerProvider && !model.includes("/") && !model.includes(":")) return {
|
|
374
|
+
model: `${ownerProvider}/${model}`,
|
|
375
|
+
provider: void 0
|
|
376
|
+
};
|
|
377
|
+
return {
|
|
378
|
+
model,
|
|
379
|
+
provider: void 0
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const discovered = discoverProvider({ env });
|
|
384
|
+
if (discovered) return {
|
|
385
|
+
model: discovered.model,
|
|
386
|
+
provider: void 0
|
|
387
|
+
};
|
|
388
|
+
const envVars = Object.values(PROVIDER_ENV_KEYS).join(", ");
|
|
389
|
+
const hint = preferences.length > 0 ? `Tried: ${preferences.join(", ")}. ` : "";
|
|
390
|
+
throw new Error(`No provider available for auto model resolution. ${hint}Set one of: ${envVars}`);
|
|
391
|
+
}
|
|
213
392
|
|
|
214
393
|
//#endregion
|
|
215
394
|
//#region src/backends/model-maps.ts
|
|
@@ -359,77 +538,13 @@ const DEFAULT_IDLE_TIMEOUT = 6e5;
|
|
|
359
538
|
* This catches unresponsive backends (e.g., nested `claude -p` inside Claude Code).
|
|
360
539
|
*/
|
|
361
540
|
const DEFAULT_STARTUP_TIMEOUT = 3e4;
|
|
362
|
-
/**
|
|
363
|
-
* Execute a command with idle timeout.
|
|
364
|
-
*
|
|
365
|
-
* The timeout resets every time the process writes to stdout or stderr.
|
|
366
|
-
* If the process goes silent for longer than `timeout` ms, it's killed.
|
|
367
|
-
*/
|
|
368
541
|
/** Minimum idle timeout to prevent accidental instant kills */
|
|
369
542
|
const MIN_TIMEOUT_MS = 1e3;
|
|
370
|
-
async function execWithIdleTimeout(options) {
|
|
371
|
-
const { command, args, cwd, onStdout } = options;
|
|
372
|
-
const timeout = Math.max(options.timeout, MIN_TIMEOUT_MS);
|
|
373
|
-
const rawStartup = options.startupTimeout !== void 0 ? options.startupTimeout : DEFAULT_STARTUP_TIMEOUT;
|
|
374
|
-
const startupTimeout = rawStartup > 0 ? Math.min(rawStartup, timeout) : 0;
|
|
375
|
-
let idleTimedOut = false;
|
|
376
|
-
let hasReceivedOutput = false;
|
|
377
|
-
let timer;
|
|
378
|
-
let stdout = "";
|
|
379
|
-
let stderr = "";
|
|
380
|
-
const subprocess = execa(command, args, {
|
|
381
|
-
cwd,
|
|
382
|
-
stdin: "ignore",
|
|
383
|
-
buffer: false
|
|
384
|
-
});
|
|
385
|
-
const resetTimer = () => {
|
|
386
|
-
clearTimeout(timer);
|
|
387
|
-
timer = setTimeout(() => {
|
|
388
|
-
idleTimedOut = true;
|
|
389
|
-
subprocess.kill();
|
|
390
|
-
}, timeout);
|
|
391
|
-
};
|
|
392
|
-
subprocess.stdout?.on("data", (chunk) => {
|
|
393
|
-
const text = chunk.toString();
|
|
394
|
-
stdout += text;
|
|
395
|
-
hasReceivedOutput = true;
|
|
396
|
-
resetTimer();
|
|
397
|
-
if (onStdout) try {
|
|
398
|
-
onStdout(text);
|
|
399
|
-
} catch (err) {
|
|
400
|
-
console.error("onStdout callback error:", err);
|
|
401
|
-
}
|
|
402
|
-
});
|
|
403
|
-
subprocess.stderr?.on("data", (chunk) => {
|
|
404
|
-
stderr += chunk.toString();
|
|
405
|
-
hasReceivedOutput = true;
|
|
406
|
-
resetTimer();
|
|
407
|
-
});
|
|
408
|
-
if (startupTimeout > 0) timer = setTimeout(() => {
|
|
409
|
-
if (!hasReceivedOutput) {
|
|
410
|
-
idleTimedOut = true;
|
|
411
|
-
subprocess.kill();
|
|
412
|
-
}
|
|
413
|
-
}, startupTimeout);
|
|
414
|
-
else resetTimer();
|
|
415
|
-
try {
|
|
416
|
-
await subprocess;
|
|
417
|
-
clearTimeout(timer);
|
|
418
|
-
return {
|
|
419
|
-
stdout: stdout.trimEnd(),
|
|
420
|
-
stderr: stderr.trimEnd()
|
|
421
|
-
};
|
|
422
|
-
} catch (error) {
|
|
423
|
-
clearTimeout(timer);
|
|
424
|
-
if (idleTimedOut) throw new IdleTimeoutError(hasReceivedOutput ? timeout : startupTimeout, stdout, stderr);
|
|
425
|
-
throw error;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
543
|
/**
|
|
429
|
-
*
|
|
430
|
-
*
|
|
544
|
+
* Core implementation shared by both sync and abortable variants.
|
|
545
|
+
* Returns the promise, an abort function, and the subprocess handle.
|
|
431
546
|
*/
|
|
432
|
-
function
|
|
547
|
+
function execWithIdleTimeoutInternal(options) {
|
|
433
548
|
const { command, args, cwd, onStdout } = options;
|
|
434
549
|
const timeout = Math.max(options.timeout, MIN_TIMEOUT_MS);
|
|
435
550
|
const rawStartup = options.startupTimeout !== void 0 ? options.startupTimeout : DEFAULT_STARTUP_TIMEOUT;
|
|
@@ -505,6 +620,23 @@ function execWithIdleTimeoutAbortable(options) {
|
|
|
505
620
|
};
|
|
506
621
|
}
|
|
507
622
|
/**
|
|
623
|
+
* Execute a command with idle timeout.
|
|
624
|
+
*
|
|
625
|
+
* The timeout resets every time the process writes to stdout or stderr.
|
|
626
|
+
* If the process goes silent for longer than `timeout` ms, it's killed.
|
|
627
|
+
*/
|
|
628
|
+
async function execWithIdleTimeout(options) {
|
|
629
|
+
const { promise } = execWithIdleTimeoutInternal(options);
|
|
630
|
+
return promise;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Execute a command with idle timeout and return abort handle.
|
|
634
|
+
* This version returns both the promise and an abort function for external control.
|
|
635
|
+
*/
|
|
636
|
+
function execWithIdleTimeoutAbortable(options) {
|
|
637
|
+
return execWithIdleTimeoutInternal(options);
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
508
640
|
* Error thrown when a process is killed due to idle timeout
|
|
509
641
|
*/
|
|
510
642
|
var IdleTimeoutError = class extends Error {
|
|
@@ -520,6 +652,45 @@ var IdleTimeoutError = class extends Error {
|
|
|
520
652
|
}
|
|
521
653
|
};
|
|
522
654
|
|
|
655
|
+
//#endregion
|
|
656
|
+
//#region src/backends/cli-helpers.ts
|
|
657
|
+
/**
|
|
658
|
+
* Shared helpers for CLI backends
|
|
659
|
+
*
|
|
660
|
+
* Eliminates duplicated error handling and availability check patterns
|
|
661
|
+
* across claude-code, codex, cursor, and opencode backends.
|
|
662
|
+
*/
|
|
663
|
+
/**
|
|
664
|
+
* Handle errors from CLI backend execution.
|
|
665
|
+
*
|
|
666
|
+
* Standardizes the error handling pattern shared by all CLI backends:
|
|
667
|
+
* 1. IdleTimeoutError → human-readable timeout message
|
|
668
|
+
* 2. Process exit error → include exit code and stderr
|
|
669
|
+
* 3. Everything else → re-throw
|
|
670
|
+
*/
|
|
671
|
+
function handleCliBackendError(error, backendName, timeout) {
|
|
672
|
+
if (error instanceof IdleTimeoutError) throw new Error(`${backendName} timed out after ${timeout}ms of inactivity`);
|
|
673
|
+
if (error && typeof error === "object" && "exitCode" in error) {
|
|
674
|
+
const execError = error;
|
|
675
|
+
throw new Error(`${backendName} failed (exit ${execError.exitCode}): ${execError.stderr || execError.shortMessage}`);
|
|
676
|
+
}
|
|
677
|
+
throw error;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Check if a CLI command is available by running `command --version`.
|
|
681
|
+
*/
|
|
682
|
+
async function checkCliAvailable(command, args = ["--version"], timeout = 5e3) {
|
|
683
|
+
try {
|
|
684
|
+
await execa(command, args, {
|
|
685
|
+
stdin: "ignore",
|
|
686
|
+
timeout
|
|
687
|
+
});
|
|
688
|
+
return true;
|
|
689
|
+
} catch {
|
|
690
|
+
return false;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
523
694
|
//#endregion
|
|
524
695
|
//#region src/backends/stream-json.ts
|
|
525
696
|
/**
|
|
@@ -786,7 +957,8 @@ function formatToolInput(input) {
|
|
|
786
957
|
*
|
|
787
958
|
* MCP Configuration:
|
|
788
959
|
* Claude supports per-invocation MCP config via --mcp-config flag.
|
|
789
|
-
*
|
|
960
|
+
* The loop writes mcp-config.json to the workspace; this backend
|
|
961
|
+
* auto-discovers it when workspace is set.
|
|
790
962
|
*
|
|
791
963
|
* @see https://docs.anthropic.com/en/docs/claude-code
|
|
792
964
|
*/
|
|
@@ -800,17 +972,6 @@ var ClaudeCodeBackend = class {
|
|
|
800
972
|
...options
|
|
801
973
|
};
|
|
802
974
|
}
|
|
803
|
-
/**
|
|
804
|
-
* Set up workspace directory with MCP config
|
|
805
|
-
* Claude uses --mcp-config flag, so we just write the config file
|
|
806
|
-
*/
|
|
807
|
-
setWorkspace(workspaceDir, mcpConfig) {
|
|
808
|
-
this.options.workspace = workspaceDir;
|
|
809
|
-
if (!existsSync(workspaceDir)) mkdirSync(workspaceDir, { recursive: true });
|
|
810
|
-
const mcpConfigPath = join(workspaceDir, "mcp-config.json");
|
|
811
|
-
writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2));
|
|
812
|
-
this.options.mcpConfigPath = mcpConfigPath;
|
|
813
|
-
}
|
|
814
975
|
async send(message, options) {
|
|
815
976
|
const args = this.buildArgs(message, options);
|
|
816
977
|
const cwd = this.options.workspace || this.options.cwd;
|
|
@@ -853,15 +1014,7 @@ var ClaudeCodeBackend = class {
|
|
|
853
1014
|
}
|
|
854
1015
|
}
|
|
855
1016
|
async isAvailable() {
|
|
856
|
-
|
|
857
|
-
await execa("claude", ["--version"], {
|
|
858
|
-
stdin: "ignore",
|
|
859
|
-
timeout: 5e3
|
|
860
|
-
});
|
|
861
|
-
return true;
|
|
862
|
-
} catch {
|
|
863
|
-
return false;
|
|
864
|
-
}
|
|
1017
|
+
return checkCliAvailable("claude");
|
|
865
1018
|
}
|
|
866
1019
|
getInfo() {
|
|
867
1020
|
return {
|
|
@@ -886,16 +1039,14 @@ var ClaudeCodeBackend = class {
|
|
|
886
1039
|
if (outputFormat === "stream-json") args.push("--verbose");
|
|
887
1040
|
if (this.options.continue) args.push("--continue");
|
|
888
1041
|
if (this.options.resume) args.push("--resume", this.options.resume);
|
|
889
|
-
|
|
1042
|
+
const mcpConfigPath = this.options.mcpConfigPath ?? (this.options.workspace ? (() => {
|
|
1043
|
+
const p = join(this.options.workspace, "mcp-config.json");
|
|
1044
|
+
return existsSync(p) ? p : void 0;
|
|
1045
|
+
})() : void 0);
|
|
1046
|
+
if (mcpConfigPath) args.push("--mcp-config", mcpConfigPath);
|
|
890
1047
|
return args;
|
|
891
1048
|
}
|
|
892
1049
|
/**
|
|
893
|
-
* Set MCP config path (for workflow integration)
|
|
894
|
-
*/
|
|
895
|
-
setMcpConfigPath(path) {
|
|
896
|
-
this.options.mcpConfigPath = path;
|
|
897
|
-
}
|
|
898
|
-
/**
|
|
899
1050
|
* Abort any running claude process
|
|
900
1051
|
*/
|
|
901
1052
|
abort() {
|
|
@@ -908,16 +1059,6 @@ var ClaudeCodeBackend = class {
|
|
|
908
1059
|
|
|
909
1060
|
//#endregion
|
|
910
1061
|
//#region src/backends/codex.ts
|
|
911
|
-
/**
|
|
912
|
-
* OpenAI Codex CLI backend
|
|
913
|
-
* Uses `codex exec` for non-interactive mode with JSON event output
|
|
914
|
-
*
|
|
915
|
-
* MCP Configuration:
|
|
916
|
-
* Codex uses project-level MCP config. Use setWorkspace() to set up
|
|
917
|
-
* a dedicated workspace directory with .codex/config.yaml for MCP settings.
|
|
918
|
-
*
|
|
919
|
-
* @see https://github.com/openai/codex
|
|
920
|
-
*/
|
|
921
1062
|
var CodexBackend = class {
|
|
922
1063
|
type = "codex";
|
|
923
1064
|
options;
|
|
@@ -927,17 +1068,6 @@ var CodexBackend = class {
|
|
|
927
1068
|
...options
|
|
928
1069
|
};
|
|
929
1070
|
}
|
|
930
|
-
/**
|
|
931
|
-
* Set up workspace directory with MCP config
|
|
932
|
-
* Creates .codex/config.yaml in the workspace with MCP server config
|
|
933
|
-
*/
|
|
934
|
-
setWorkspace(workspaceDir, mcpConfig) {
|
|
935
|
-
this.options.workspace = workspaceDir;
|
|
936
|
-
const codexDir = join(workspaceDir, ".codex");
|
|
937
|
-
if (!existsSync(codexDir)) mkdirSync(codexDir, { recursive: true });
|
|
938
|
-
const codexConfig = { mcp_servers: mcpConfig.mcpServers };
|
|
939
|
-
writeFileSync(join(codexDir, "config.yaml"), stringify(codexConfig));
|
|
940
|
-
}
|
|
941
1071
|
async send(message, _options) {
|
|
942
1072
|
const args = this.buildArgs(message);
|
|
943
1073
|
const cwd = this.options.workspace || this.options.cwd;
|
|
@@ -952,24 +1082,11 @@ var CodexBackend = class {
|
|
|
952
1082
|
});
|
|
953
1083
|
return extractCodexResult(stdout);
|
|
954
1084
|
} catch (error) {
|
|
955
|
-
|
|
956
|
-
if (error && typeof error === "object" && "exitCode" in error) {
|
|
957
|
-
const execError = error;
|
|
958
|
-
throw new Error(`codex failed (exit ${execError.exitCode}): ${execError.stderr || execError.shortMessage}`);
|
|
959
|
-
}
|
|
960
|
-
throw error;
|
|
1085
|
+
handleCliBackendError(error, "codex", timeout);
|
|
961
1086
|
}
|
|
962
1087
|
}
|
|
963
1088
|
async isAvailable() {
|
|
964
|
-
|
|
965
|
-
await execa("codex", ["--version"], {
|
|
966
|
-
stdin: "ignore",
|
|
967
|
-
timeout: 5e3
|
|
968
|
-
});
|
|
969
|
-
return true;
|
|
970
|
-
} catch {
|
|
971
|
-
return false;
|
|
972
|
-
}
|
|
1089
|
+
return checkCliAvailable("codex");
|
|
973
1090
|
}
|
|
974
1091
|
getInfo() {
|
|
975
1092
|
return {
|
|
@@ -993,16 +1110,6 @@ var CodexBackend = class {
|
|
|
993
1110
|
|
|
994
1111
|
//#endregion
|
|
995
1112
|
//#region src/backends/cursor.ts
|
|
996
|
-
/**
|
|
997
|
-
* Cursor CLI backend
|
|
998
|
-
* Uses `cursor agent -p` for non-interactive mode with stream-json output
|
|
999
|
-
*
|
|
1000
|
-
* MCP Configuration:
|
|
1001
|
-
* Cursor uses project-level MCP config via .cursor/mcp.json in the workspace.
|
|
1002
|
-
* Use setWorkspace() to set up a dedicated workspace with MCP config.
|
|
1003
|
-
*
|
|
1004
|
-
* @see https://docs.cursor.com/context/model-context-protocol
|
|
1005
|
-
*/
|
|
1006
1113
|
var CursorBackend = class {
|
|
1007
1114
|
type = "cursor";
|
|
1008
1115
|
options;
|
|
@@ -1019,16 +1126,6 @@ var CursorBackend = class {
|
|
|
1019
1126
|
...options
|
|
1020
1127
|
};
|
|
1021
1128
|
}
|
|
1022
|
-
/**
|
|
1023
|
-
* Set up workspace directory with MCP config
|
|
1024
|
-
* Creates .cursor/mcp.json in the workspace
|
|
1025
|
-
*/
|
|
1026
|
-
setWorkspace(workspaceDir, mcpConfig) {
|
|
1027
|
-
this.options.workspace = workspaceDir;
|
|
1028
|
-
const cursorDir = join(workspaceDir, ".cursor");
|
|
1029
|
-
if (!existsSync(cursorDir)) mkdirSync(cursorDir, { recursive: true });
|
|
1030
|
-
writeFileSync(join(cursorDir, "mcp.json"), JSON.stringify(mcpConfig, null, 2));
|
|
1031
|
-
}
|
|
1032
1129
|
async send(message, _options) {
|
|
1033
1130
|
const { command, args } = await this.buildCommand(message);
|
|
1034
1131
|
const cwd = this.options.workspace || this.options.cwd;
|
|
@@ -1043,12 +1140,7 @@ var CursorBackend = class {
|
|
|
1043
1140
|
});
|
|
1044
1141
|
return extractClaudeResult(stdout);
|
|
1045
1142
|
} catch (error) {
|
|
1046
|
-
|
|
1047
|
-
if (error && typeof error === "object" && "exitCode" in error) {
|
|
1048
|
-
const execError = error;
|
|
1049
|
-
throw new Error(`cursor agent failed (exit ${execError.exitCode}): ${execError.stderr || execError.shortMessage}`);
|
|
1050
|
-
}
|
|
1051
|
-
throw error;
|
|
1143
|
+
handleCliBackendError(error, "cursor agent", timeout);
|
|
1052
1144
|
}
|
|
1053
1145
|
}
|
|
1054
1146
|
async isAvailable() {
|
|
@@ -1069,22 +1161,14 @@ var CursorBackend = class {
|
|
|
1069
1161
|
*/
|
|
1070
1162
|
async resolveStyle() {
|
|
1071
1163
|
if (this.resolvedStyle !== null) return this.resolvedStyle;
|
|
1072
|
-
|
|
1073
|
-
await execa("cursor", ["agent", "--version"], {
|
|
1074
|
-
stdin: "ignore",
|
|
1075
|
-
timeout: 2e3
|
|
1076
|
-
});
|
|
1164
|
+
if (await checkCliAvailable("cursor", ["agent", "--version"], 2e3)) {
|
|
1077
1165
|
this.resolvedStyle = "subcommand";
|
|
1078
1166
|
return "subcommand";
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
await execa("agent", ["--version"], {
|
|
1082
|
-
stdin: "ignore",
|
|
1083
|
-
timeout: 2e3
|
|
1084
|
-
});
|
|
1167
|
+
}
|
|
1168
|
+
if (await checkCliAvailable("agent", ["--version"], 2e3)) {
|
|
1085
1169
|
this.resolvedStyle = "direct";
|
|
1086
1170
|
return "direct";
|
|
1087
|
-
}
|
|
1171
|
+
}
|
|
1088
1172
|
return null;
|
|
1089
1173
|
}
|
|
1090
1174
|
async buildCommand(message) {
|
|
@@ -1111,16 +1195,6 @@ var CursorBackend = class {
|
|
|
1111
1195
|
|
|
1112
1196
|
//#endregion
|
|
1113
1197
|
//#region src/backends/opencode.ts
|
|
1114
|
-
/**
|
|
1115
|
-
* OpenCode CLI backend
|
|
1116
|
-
* Uses `opencode run` for non-interactive mode with JSON event output
|
|
1117
|
-
*
|
|
1118
|
-
* MCP Configuration:
|
|
1119
|
-
* OpenCode uses project-level MCP config via opencode.json in the workspace.
|
|
1120
|
-
* Use setWorkspace() to set up a dedicated workspace with MCP config.
|
|
1121
|
-
*
|
|
1122
|
-
* @see https://opencode.ai/docs/
|
|
1123
|
-
*/
|
|
1124
1198
|
var OpenCodeBackend = class {
|
|
1125
1199
|
type = "opencode";
|
|
1126
1200
|
options;
|
|
@@ -1130,29 +1204,6 @@ var OpenCodeBackend = class {
|
|
|
1130
1204
|
...options
|
|
1131
1205
|
};
|
|
1132
1206
|
}
|
|
1133
|
-
/**
|
|
1134
|
-
* Set up workspace directory with MCP config
|
|
1135
|
-
* Creates opencode.json in the workspace with MCP server config
|
|
1136
|
-
*/
|
|
1137
|
-
setWorkspace(workspaceDir, mcpConfig) {
|
|
1138
|
-
this.options.workspace = workspaceDir;
|
|
1139
|
-
if (!existsSync(workspaceDir)) mkdirSync(workspaceDir, { recursive: true });
|
|
1140
|
-
const opencodeMcp = {};
|
|
1141
|
-
for (const [name, config] of Object.entries(mcpConfig.mcpServers)) {
|
|
1142
|
-
const serverConfig = config;
|
|
1143
|
-
opencodeMcp[name] = {
|
|
1144
|
-
type: "local",
|
|
1145
|
-
command: [serverConfig.command, ...serverConfig.args || []],
|
|
1146
|
-
enabled: true,
|
|
1147
|
-
...serverConfig.env ? { environment: serverConfig.env } : {}
|
|
1148
|
-
};
|
|
1149
|
-
}
|
|
1150
|
-
const opencodeConfig = {
|
|
1151
|
-
$schema: "https://opencode.ai/config.json",
|
|
1152
|
-
mcp: opencodeMcp
|
|
1153
|
-
};
|
|
1154
|
-
writeFileSync(join(workspaceDir, "opencode.json"), JSON.stringify(opencodeConfig, null, 2));
|
|
1155
|
-
}
|
|
1156
1207
|
async send(message, _options) {
|
|
1157
1208
|
const args = this.buildArgs(message);
|
|
1158
1209
|
const cwd = this.options.workspace || this.options.cwd;
|
|
@@ -1167,24 +1218,11 @@ var OpenCodeBackend = class {
|
|
|
1167
1218
|
});
|
|
1168
1219
|
return extractOpenCodeResult(stdout);
|
|
1169
1220
|
} catch (error) {
|
|
1170
|
-
|
|
1171
|
-
if (error && typeof error === "object" && "exitCode" in error) {
|
|
1172
|
-
const execError = error;
|
|
1173
|
-
throw new Error(`opencode failed (exit ${execError.exitCode}): ${execError.stderr || execError.shortMessage}`);
|
|
1174
|
-
}
|
|
1175
|
-
throw error;
|
|
1221
|
+
handleCliBackendError(error, "opencode", timeout);
|
|
1176
1222
|
}
|
|
1177
1223
|
}
|
|
1178
1224
|
async isAvailable() {
|
|
1179
|
-
|
|
1180
|
-
await execa("opencode", ["--version"], {
|
|
1181
|
-
stdin: "ignore",
|
|
1182
|
-
timeout: 5e3
|
|
1183
|
-
});
|
|
1184
|
-
return true;
|
|
1185
|
-
} catch {
|
|
1186
|
-
return false;
|
|
1187
|
-
}
|
|
1225
|
+
return checkCliAvailable("opencode");
|
|
1188
1226
|
}
|
|
1189
1227
|
getInfo() {
|
|
1190
1228
|
return {
|
|
@@ -1316,8 +1354,8 @@ var SdkBackend = class {
|
|
|
1316
1354
|
* Mock AI Backend for testing
|
|
1317
1355
|
*
|
|
1318
1356
|
* In single-agent mode, provides a simple echo send().
|
|
1319
|
-
* In workflow mode, the
|
|
1320
|
-
* via the mock runner strategy (
|
|
1357
|
+
* In workflow mode, the loop handles MCP tool orchestration
|
|
1358
|
+
* via the mock runner strategy (loop/mock-runner.ts).
|
|
1321
1359
|
*/
|
|
1322
1360
|
var MockAIBackend = class {
|
|
1323
1361
|
type = "mock";
|
|
@@ -1443,4 +1481,4 @@ async function listBackends() {
|
|
|
1443
1481
|
}
|
|
1444
1482
|
|
|
1445
1483
|
//#endregion
|
|
1446
|
-
export { parseModel as A, CLAUDE_MODEL_MAP as C, SDK_MODEL_ALIASES as D, OPENCODE_MODEL_MAP as E, createModelWithProvider as F, getDefaultModel as I, SUPPORTED_PROVIDERS as M, createModel as N, getModelForBackend as O, createModelAsync as P, BACKEND_DEFAULT_MODELS as S, CURSOR_MODEL_MAP as T, extractCodexResult as _, createMockBackend as a, execWithIdleTimeout as b, extractOpenCodeResult as c, CodexBackend as d, ClaudeCodeBackend as f, extractClaudeResult as g, createStreamParser as h, MockAIBackend as i, FRONTIER_MODELS as j, normalizeBackendType as k, opencodeAdapter as l, codexAdapter as m, createBackend as n, SdkBackend as o, claudeAdapter as p, listBackends as r, OpenCodeBackend as s, checkBackends as t, CursorBackend as u, formatEvent as v, CODEX_MODEL_MAP as w, DEFAULT_IDLE_TIMEOUT as x, IdleTimeoutError as y };
|
|
1484
|
+
export { parseModel as A, CLAUDE_MODEL_MAP as C, SDK_MODEL_ALIASES as D, OPENCODE_MODEL_MAP as E, createModelWithProvider as F, getDefaultModel as I, isAutoProvider as L, SUPPORTED_PROVIDERS as M, createModel as N, getModelForBackend as O, createModelAsync as P, resolveModelFallback as R, BACKEND_DEFAULT_MODELS as S, CURSOR_MODEL_MAP as T, extractCodexResult as _, createMockBackend as a, execWithIdleTimeout as b, extractOpenCodeResult as c, CodexBackend as d, ClaudeCodeBackend as f, extractClaudeResult as g, createStreamParser as h, MockAIBackend as i, FRONTIER_MODELS as j, normalizeBackendType as k, opencodeAdapter as l, codexAdapter as m, createBackend as n, SdkBackend as o, claudeAdapter as p, listBackends as r, OpenCodeBackend as s, checkBackends as t, CursorBackend as u, formatEvent as v, CODEX_MODEL_MAP as w, DEFAULT_IDLE_TIMEOUT as x, IdleTimeoutError as y };
|