agent-worker 0.12.0 → 0.14.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/{backends-DLaP0rMW.mjs → backends-C6WBIn9H.mjs} +345 -108
- package/dist/backends-Cv0oM9Ru.mjs +3 -0
- package/dist/cli/index.mjs +1428 -108
- package/dist/context-CzqQeThq.mjs +4 -0
- package/dist/index.d.mts +115 -64
- package/dist/index.mjs +446 -3
- package/dist/{memory-provider-BtLYtdQH.mjs → memory-provider-0nuDxzYQ.mjs} +1 -1
- package/dist/runner-DV86expc.mjs +663 -0
- package/dist/{workflow-CNlUyGit.mjs → workflow-DogkVjOs.mjs} +60 -6
- package/package.json +5 -2
- package/dist/backends-DG5igQii.mjs +0 -3
- package/dist/context-BqEyt2SF.mjs +0 -4
- package/dist/logger-Bfdo83xL.mjs +0 -63
- package/dist/runner-CQJYnM7D.mjs +0 -1489
- package/dist/worker-CJ5_b2_q.mjs +0 -446
|
@@ -6,30 +6,45 @@ import { stringify } from "yaml";
|
|
|
6
6
|
|
|
7
7
|
//#region src/agent/models.ts
|
|
8
8
|
const providerCache = {};
|
|
9
|
+
/** Provider SDK package mapping */
|
|
10
|
+
const PROVIDER_PACKAGES = {
|
|
11
|
+
anthropic: {
|
|
12
|
+
package: "@ai-sdk/anthropic",
|
|
13
|
+
export: "anthropic"
|
|
14
|
+
},
|
|
15
|
+
openai: {
|
|
16
|
+
package: "@ai-sdk/openai",
|
|
17
|
+
export: "openai"
|
|
18
|
+
},
|
|
19
|
+
deepseek: {
|
|
20
|
+
package: "@ai-sdk/deepseek",
|
|
21
|
+
export: "deepseek"
|
|
22
|
+
},
|
|
23
|
+
google: {
|
|
24
|
+
package: "@ai-sdk/google",
|
|
25
|
+
export: "google"
|
|
26
|
+
},
|
|
27
|
+
groq: {
|
|
28
|
+
package: "@ai-sdk/groq",
|
|
29
|
+
export: "groq"
|
|
30
|
+
},
|
|
31
|
+
mistral: {
|
|
32
|
+
package: "@ai-sdk/mistral",
|
|
33
|
+
export: "mistral"
|
|
34
|
+
},
|
|
35
|
+
xai: {
|
|
36
|
+
package: "@ai-sdk/xai",
|
|
37
|
+
export: "xai"
|
|
38
|
+
}
|
|
39
|
+
};
|
|
9
40
|
/**
|
|
10
|
-
* Lazy load a provider, caching the result
|
|
11
|
-
*
|
|
41
|
+
* Lazy load a provider SDK, caching the result.
|
|
42
|
+
* Only caches standard providers (no custom baseURL/apiKey).
|
|
12
43
|
*/
|
|
13
|
-
async function loadProvider(name, packageName, exportName
|
|
44
|
+
async function loadProvider(name, packageName, exportName) {
|
|
14
45
|
if (name in providerCache) return providerCache[name] ?? null;
|
|
15
46
|
try {
|
|
16
|
-
const
|
|
17
|
-
if (options?.baseURL || options?.apiKeyEnvVar) {
|
|
18
|
-
const createProvider = module[`create${exportName.charAt(0).toUpperCase() + exportName.slice(1)}`];
|
|
19
|
-
if (createProvider) {
|
|
20
|
-
const providerOptions = {};
|
|
21
|
-
if (options.baseURL) providerOptions.baseURL = options.baseURL;
|
|
22
|
-
if (options.apiKeyEnvVar) {
|
|
23
|
-
const apiKey = process.env[options.apiKeyEnvVar];
|
|
24
|
-
if (!apiKey) throw new Error(`Environment variable ${options.apiKeyEnvVar} is not set (required for ${name} provider)`);
|
|
25
|
-
providerOptions.apiKey = apiKey;
|
|
26
|
-
}
|
|
27
|
-
const provider = createProvider(providerOptions);
|
|
28
|
-
providerCache[name] = provider;
|
|
29
|
-
return provider;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
const exportedProvider = module[exportName];
|
|
47
|
+
const exportedProvider = (await import(packageName))[exportName];
|
|
33
48
|
providerCache[name] = exportedProvider;
|
|
34
49
|
return exportedProvider;
|
|
35
50
|
} catch {
|
|
@@ -38,6 +53,55 @@ async function loadProvider(name, packageName, exportName, options) {
|
|
|
38
53
|
}
|
|
39
54
|
}
|
|
40
55
|
/**
|
|
56
|
+
* Create a provider instance with custom baseURL and/or apiKey.
|
|
57
|
+
* Not cached — each call creates a fresh instance.
|
|
58
|
+
*/
|
|
59
|
+
async function createCustomProvider(packageName, exportName, options) {
|
|
60
|
+
const createFn = (await import(packageName))[`create${exportName.charAt(0).toUpperCase() + exportName.slice(1)}`];
|
|
61
|
+
if (!createFn) throw new Error(`Package ${packageName} does not export create${exportName.charAt(0).toUpperCase() + exportName.slice(1)}`);
|
|
62
|
+
return createFn(options);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Resolve api_key field: '$ENV_VAR' → process.env.ENV_VAR, literal → as-is
|
|
66
|
+
*/
|
|
67
|
+
function resolveApiKey(apiKey) {
|
|
68
|
+
if (apiKey.startsWith("$")) {
|
|
69
|
+
const envVar = apiKey.slice(1);
|
|
70
|
+
const value = process.env[envVar];
|
|
71
|
+
if (!value) throw new Error(`Environment variable ${envVar} is not set`);
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
return apiKey;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Create a model using explicit provider configuration.
|
|
78
|
+
* Use this when provider details (base_url, api_key) are specified separately from the model name.
|
|
79
|
+
*
|
|
80
|
+
* Example:
|
|
81
|
+
* createModelWithProvider("MiniMax-M2.5", { name: "anthropic", base_url: "https://api.minimax.io/anthropic/v1", api_key: "$MINIMAX_API_KEY" })
|
|
82
|
+
*/
|
|
83
|
+
async function createModelWithProvider(modelName, provider) {
|
|
84
|
+
if (typeof provider === "string") {
|
|
85
|
+
const pkg = PROVIDER_PACKAGES[provider];
|
|
86
|
+
if (!pkg) throw new Error(`Unknown provider: ${provider}. Supported: ${Object.keys(PROVIDER_PACKAGES).join(", ")}`);
|
|
87
|
+
const providerFn = await loadProvider(provider, pkg.package, pkg.export);
|
|
88
|
+
if (!providerFn) throw new Error(`Install ${pkg.package} to use ${provider} models directly`);
|
|
89
|
+
return providerFn(modelName);
|
|
90
|
+
}
|
|
91
|
+
const { name, base_url, api_key } = provider;
|
|
92
|
+
const pkg = PROVIDER_PACKAGES[name];
|
|
93
|
+
if (!pkg) throw new Error(`Unknown provider: ${name}. Supported: ${Object.keys(PROVIDER_PACKAGES).join(", ")}`);
|
|
94
|
+
if (!base_url && !api_key) {
|
|
95
|
+
const providerFn = await loadProvider(name, pkg.package, pkg.export);
|
|
96
|
+
if (!providerFn) throw new Error(`Install ${pkg.package} to use ${name} models directly`);
|
|
97
|
+
return providerFn(modelName);
|
|
98
|
+
}
|
|
99
|
+
const opts = {};
|
|
100
|
+
if (base_url) opts.baseURL = base_url;
|
|
101
|
+
if (api_key) opts.apiKey = resolveApiKey(api_key);
|
|
102
|
+
return (await createCustomProvider(pkg.package, pkg.export, opts))(modelName);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
41
105
|
* Parse model identifier and return the appropriate provider model
|
|
42
106
|
*
|
|
43
107
|
* Supports three formats:
|
|
@@ -89,61 +153,14 @@ async function createModelAsync(modelId) {
|
|
|
89
153
|
const provider = modelId.slice(0, colonIndex);
|
|
90
154
|
const modelName = modelId.slice(colonIndex + 1);
|
|
91
155
|
if (!modelName) throw new Error(`Invalid model identifier: ${modelId}. Model name is required.`);
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
export: "anthropic"
|
|
96
|
-
},
|
|
97
|
-
openai: {
|
|
98
|
-
package: "@ai-sdk/openai",
|
|
99
|
-
export: "openai"
|
|
100
|
-
},
|
|
101
|
-
deepseek: {
|
|
102
|
-
package: "@ai-sdk/deepseek",
|
|
103
|
-
export: "deepseek"
|
|
104
|
-
},
|
|
105
|
-
google: {
|
|
106
|
-
package: "@ai-sdk/google",
|
|
107
|
-
export: "google"
|
|
108
|
-
},
|
|
109
|
-
groq: {
|
|
110
|
-
package: "@ai-sdk/groq",
|
|
111
|
-
export: "groq"
|
|
112
|
-
},
|
|
113
|
-
mistral: {
|
|
114
|
-
package: "@ai-sdk/mistral",
|
|
115
|
-
export: "mistral"
|
|
116
|
-
},
|
|
117
|
-
xai: {
|
|
118
|
-
package: "@ai-sdk/xai",
|
|
119
|
-
export: "xai"
|
|
120
|
-
},
|
|
121
|
-
minimax: {
|
|
122
|
-
package: "@ai-sdk/anthropic",
|
|
123
|
-
export: "anthropic",
|
|
124
|
-
options: {
|
|
125
|
-
baseURL: "https://api.minimax.io/anthropic/v1",
|
|
126
|
-
apiKeyEnvVar: "MINIMAX_API_KEY"
|
|
127
|
-
}
|
|
128
|
-
},
|
|
129
|
-
minimax_cn: {
|
|
130
|
-
package: "@ai-sdk/anthropic",
|
|
131
|
-
export: "anthropic",
|
|
132
|
-
options: {
|
|
133
|
-
baseURL: "https://api.minimaxi.com/anthropic/v1",
|
|
134
|
-
apiKeyEnvVar: "MINIMAX_CN_API_KEY"
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
const config = providerConfigs[provider];
|
|
139
|
-
if (!config) throw new Error(`Unknown provider: ${provider}. Supported: ${Object.keys(providerConfigs).join(", ")}. Or use gateway format: provider/model (e.g., openai/gpt-5.2)`);
|
|
140
|
-
const providerFn = await loadProvider(provider, config.package, config.export, config.options);
|
|
156
|
+
const config = PROVIDER_PACKAGES[provider];
|
|
157
|
+
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
|
+
const providerFn = await loadProvider(provider, config.package, config.export);
|
|
141
159
|
if (!providerFn) throw new Error(`Install ${config.package} to use ${provider} models directly`);
|
|
142
160
|
return providerFn(modelName);
|
|
143
161
|
}
|
|
144
162
|
/**
|
|
145
163
|
* List of supported providers for direct access
|
|
146
|
-
* Note: minimax uses Claude-compatible API via @ai-sdk/anthropic with custom baseURL
|
|
147
164
|
*/
|
|
148
165
|
const SUPPORTED_PROVIDERS = [
|
|
149
166
|
"anthropic",
|
|
@@ -152,8 +169,7 @@ const SUPPORTED_PROVIDERS = [
|
|
|
152
169
|
"google",
|
|
153
170
|
"groq",
|
|
154
171
|
"mistral",
|
|
155
|
-
"xai"
|
|
156
|
-
"minimax"
|
|
172
|
+
"xai"
|
|
157
173
|
];
|
|
158
174
|
/**
|
|
159
175
|
* Default provider when none specified
|
|
@@ -192,8 +208,7 @@ const FRONTIER_MODELS = {
|
|
|
192
208
|
"pixtral-large-latest",
|
|
193
209
|
"magistral-medium-2506"
|
|
194
210
|
],
|
|
195
|
-
xai: ["grok-4", "grok-4-fast-reasoning"]
|
|
196
|
-
minimax: ["MiniMax-M2"]
|
|
211
|
+
xai: ["grok-4", "grok-4-fast-reasoning"]
|
|
197
212
|
};
|
|
198
213
|
|
|
199
214
|
//#endregion
|
|
@@ -209,7 +224,8 @@ const BACKEND_DEFAULT_MODELS = {
|
|
|
209
224
|
default: "claude-sonnet-4-5",
|
|
210
225
|
claude: "sonnet",
|
|
211
226
|
cursor: "sonnet-4.5",
|
|
212
|
-
codex: "gpt-5.2-codex"
|
|
227
|
+
codex: "gpt-5.2-codex",
|
|
228
|
+
opencode: "deepseek/deepseek-chat"
|
|
213
229
|
};
|
|
214
230
|
/**
|
|
215
231
|
* Model aliases for SDK (Anthropic format)
|
|
@@ -272,6 +288,22 @@ const CODEX_MODEL_MAP = {
|
|
|
272
288
|
"o3-mini": "o3-mini"
|
|
273
289
|
};
|
|
274
290
|
/**
|
|
291
|
+
* Model translation for OpenCode CLI backend
|
|
292
|
+
* OpenCode uses provider/model format natively
|
|
293
|
+
*/
|
|
294
|
+
const OPENCODE_MODEL_MAP = {
|
|
295
|
+
"deepseek-chat": "deepseek/deepseek-chat",
|
|
296
|
+
"deepseek-reasoner": "deepseek/deepseek-reasoner",
|
|
297
|
+
"deepseek/deepseek-chat": "deepseek/deepseek-chat",
|
|
298
|
+
"deepseek/deepseek-reasoner": "deepseek/deepseek-reasoner",
|
|
299
|
+
sonnet: "anthropic/claude-sonnet-4-5-20250514",
|
|
300
|
+
opus: "anthropic/claude-opus-4-20250514",
|
|
301
|
+
"claude-sonnet-4-5": "anthropic/claude-sonnet-4-5-20250514",
|
|
302
|
+
"claude-opus-4": "anthropic/claude-opus-4-20250514",
|
|
303
|
+
"gpt-5.2": "openai/gpt-5.2",
|
|
304
|
+
o3: "openai/o3"
|
|
305
|
+
};
|
|
306
|
+
/**
|
|
275
307
|
* Get the model name for a specific backend
|
|
276
308
|
* Translates generic model names to backend-specific format
|
|
277
309
|
*/
|
|
@@ -283,6 +315,7 @@ function getModelForBackend(model, backend) {
|
|
|
283
315
|
case "cursor": return CURSOR_MODEL_MAP[model] || CURSOR_MODEL_MAP[normalizedModel] || normalizedModel;
|
|
284
316
|
case "claude": return CLAUDE_MODEL_MAP[model] || CLAUDE_MODEL_MAP[normalizedModel] || normalizedModel;
|
|
285
317
|
case "codex": return CODEX_MODEL_MAP[model] || CODEX_MODEL_MAP[normalizedModel] || normalizedModel;
|
|
318
|
+
case "opencode": return OPENCODE_MODEL_MAP[model] || OPENCODE_MODEL_MAP[normalizedModel] || model;
|
|
286
319
|
default: return normalizedModel;
|
|
287
320
|
}
|
|
288
321
|
}
|
|
@@ -321,6 +354,12 @@ const DEFAULT_IDLE_TIMEOUT = 6e5;
|
|
|
321
354
|
* producing output (tool calls, analysis, etc.).
|
|
322
355
|
*/
|
|
323
356
|
/**
|
|
357
|
+
* Default startup timeout (30 seconds).
|
|
358
|
+
* If the process produces zero output within this window, it's killed.
|
|
359
|
+
* This catches unresponsive backends (e.g., nested `claude -p` inside Claude Code).
|
|
360
|
+
*/
|
|
361
|
+
const DEFAULT_STARTUP_TIMEOUT = 3e4;
|
|
362
|
+
/**
|
|
324
363
|
* Execute a command with idle timeout.
|
|
325
364
|
*
|
|
326
365
|
* The timeout resets every time the process writes to stdout or stderr.
|
|
@@ -331,7 +370,10 @@ const MIN_TIMEOUT_MS = 1e3;
|
|
|
331
370
|
async function execWithIdleTimeout(options) {
|
|
332
371
|
const { command, args, cwd, onStdout } = options;
|
|
333
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;
|
|
334
375
|
let idleTimedOut = false;
|
|
376
|
+
let hasReceivedOutput = false;
|
|
335
377
|
let timer;
|
|
336
378
|
let stdout = "";
|
|
337
379
|
let stderr = "";
|
|
@@ -350,6 +392,7 @@ async function execWithIdleTimeout(options) {
|
|
|
350
392
|
subprocess.stdout?.on("data", (chunk) => {
|
|
351
393
|
const text = chunk.toString();
|
|
352
394
|
stdout += text;
|
|
395
|
+
hasReceivedOutput = true;
|
|
353
396
|
resetTimer();
|
|
354
397
|
if (onStdout) try {
|
|
355
398
|
onStdout(text);
|
|
@@ -359,9 +402,16 @@ async function execWithIdleTimeout(options) {
|
|
|
359
402
|
});
|
|
360
403
|
subprocess.stderr?.on("data", (chunk) => {
|
|
361
404
|
stderr += chunk.toString();
|
|
405
|
+
hasReceivedOutput = true;
|
|
362
406
|
resetTimer();
|
|
363
407
|
});
|
|
364
|
-
|
|
408
|
+
if (startupTimeout > 0) timer = setTimeout(() => {
|
|
409
|
+
if (!hasReceivedOutput) {
|
|
410
|
+
idleTimedOut = true;
|
|
411
|
+
subprocess.kill();
|
|
412
|
+
}
|
|
413
|
+
}, startupTimeout);
|
|
414
|
+
else resetTimer();
|
|
365
415
|
try {
|
|
366
416
|
await subprocess;
|
|
367
417
|
clearTimeout(timer);
|
|
@@ -371,7 +421,7 @@ async function execWithIdleTimeout(options) {
|
|
|
371
421
|
};
|
|
372
422
|
} catch (error) {
|
|
373
423
|
clearTimeout(timer);
|
|
374
|
-
if (idleTimedOut) throw new IdleTimeoutError(timeout, stdout, stderr);
|
|
424
|
+
if (idleTimedOut) throw new IdleTimeoutError(hasReceivedOutput ? timeout : startupTimeout, stdout, stderr);
|
|
375
425
|
throw error;
|
|
376
426
|
}
|
|
377
427
|
}
|
|
@@ -382,7 +432,10 @@ async function execWithIdleTimeout(options) {
|
|
|
382
432
|
function execWithIdleTimeoutAbortable(options) {
|
|
383
433
|
const { command, args, cwd, onStdout } = options;
|
|
384
434
|
const timeout = Math.max(options.timeout, MIN_TIMEOUT_MS);
|
|
435
|
+
const rawStartup = options.startupTimeout !== void 0 ? options.startupTimeout : DEFAULT_STARTUP_TIMEOUT;
|
|
436
|
+
const startupTimeout = rawStartup > 0 ? Math.min(rawStartup, timeout) : 0;
|
|
385
437
|
let idleTimedOut = false;
|
|
438
|
+
let hasReceivedOutput = false;
|
|
386
439
|
let timer;
|
|
387
440
|
let stdout = "";
|
|
388
441
|
let stderr = "";
|
|
@@ -402,6 +455,7 @@ function execWithIdleTimeoutAbortable(options) {
|
|
|
402
455
|
subprocess.stdout?.on("data", (chunk) => {
|
|
403
456
|
const text = chunk.toString();
|
|
404
457
|
stdout += text;
|
|
458
|
+
hasReceivedOutput = true;
|
|
405
459
|
resetTimer();
|
|
406
460
|
if (onStdout) try {
|
|
407
461
|
onStdout(text);
|
|
@@ -411,9 +465,16 @@ function execWithIdleTimeoutAbortable(options) {
|
|
|
411
465
|
});
|
|
412
466
|
subprocess.stderr?.on("data", (chunk) => {
|
|
413
467
|
stderr += chunk.toString();
|
|
468
|
+
hasReceivedOutput = true;
|
|
414
469
|
resetTimer();
|
|
415
470
|
});
|
|
416
|
-
|
|
471
|
+
if (startupTimeout > 0) timer = setTimeout(() => {
|
|
472
|
+
if (!hasReceivedOutput) {
|
|
473
|
+
idleTimedOut = true;
|
|
474
|
+
subprocess.kill();
|
|
475
|
+
}
|
|
476
|
+
}, startupTimeout);
|
|
477
|
+
else resetTimer();
|
|
417
478
|
const abort = () => {
|
|
418
479
|
if (!isAborted) {
|
|
419
480
|
isAborted = true;
|
|
@@ -436,7 +497,7 @@ function execWithIdleTimeoutAbortable(options) {
|
|
|
436
497
|
} catch (error) {
|
|
437
498
|
clearTimeout(timer);
|
|
438
499
|
if (isAborted) throw new Error("Process aborted by user");
|
|
439
|
-
if (idleTimedOut) throw new IdleTimeoutError(timeout, stdout, stderr);
|
|
500
|
+
if (idleTimedOut) throw new IdleTimeoutError(hasReceivedOutput ? timeout : startupTimeout, stdout, stderr);
|
|
440
501
|
throw error;
|
|
441
502
|
}
|
|
442
503
|
})(),
|
|
@@ -780,7 +841,10 @@ var ClaudeCodeBackend = class {
|
|
|
780
841
|
return { content: stdout.trim() };
|
|
781
842
|
} catch (error) {
|
|
782
843
|
this.currentAbort = void 0;
|
|
783
|
-
if (error instanceof IdleTimeoutError)
|
|
844
|
+
if (error instanceof IdleTimeoutError) {
|
|
845
|
+
if (error.stdout === "" && error.stderr === "") throw new Error(`claude produced no output within ${error.timeout}ms. This often happens when running nested 'claude -p' inside an existing Claude Code session. Consider using the SDK backend (model: "anthropic/claude-sonnet-4-5") instead.`);
|
|
846
|
+
throw new Error(`claude timed out after ${timeout}ms of inactivity`);
|
|
847
|
+
}
|
|
784
848
|
if (error && typeof error === "object" && "exitCode" in error) {
|
|
785
849
|
const execError = error;
|
|
786
850
|
throw new Error(`claude failed (exit ${execError.exitCode}): ${execError.stderr || execError.shortMessage}`);
|
|
@@ -931,8 +995,7 @@ var CodexBackend = class {
|
|
|
931
995
|
//#region src/backends/cursor.ts
|
|
932
996
|
/**
|
|
933
997
|
* Cursor CLI backend
|
|
934
|
-
* Uses `cursor agent -p`
|
|
935
|
-
* for non-interactive mode with stream-json output
|
|
998
|
+
* Uses `cursor agent -p` for non-interactive mode with stream-json output
|
|
936
999
|
*
|
|
937
1000
|
* MCP Configuration:
|
|
938
1001
|
* Cursor uses project-level MCP config via .cursor/mcp.json in the workspace.
|
|
@@ -943,8 +1006,13 @@ var CodexBackend = class {
|
|
|
943
1006
|
var CursorBackend = class {
|
|
944
1007
|
type = "cursor";
|
|
945
1008
|
options;
|
|
946
|
-
/**
|
|
947
|
-
|
|
1009
|
+
/**
|
|
1010
|
+
* Resolved command style:
|
|
1011
|
+
* - "subcommand": `cursor agent -p ...` (IDE-bundled CLI)
|
|
1012
|
+
* - "direct": `agent -p ...` (standalone install via cursor.com/install)
|
|
1013
|
+
* - null: not yet resolved
|
|
1014
|
+
*/
|
|
1015
|
+
resolvedStyle = null;
|
|
948
1016
|
constructor(options = {}) {
|
|
949
1017
|
this.options = {
|
|
950
1018
|
timeout: DEFAULT_IDLE_TIMEOUT,
|
|
@@ -984,7 +1052,7 @@ var CursorBackend = class {
|
|
|
984
1052
|
}
|
|
985
1053
|
}
|
|
986
1054
|
async isAvailable() {
|
|
987
|
-
return await this.
|
|
1055
|
+
return await this.resolveStyle() !== null;
|
|
988
1056
|
}
|
|
989
1057
|
getInfo() {
|
|
990
1058
|
return {
|
|
@@ -993,32 +1061,34 @@ var CursorBackend = class {
|
|
|
993
1061
|
};
|
|
994
1062
|
}
|
|
995
1063
|
/**
|
|
996
|
-
* Resolve which cursor command is available.
|
|
997
|
-
*
|
|
1064
|
+
* Resolve which cursor command style is available.
|
|
1065
|
+
* Tries in order:
|
|
1066
|
+
* 1. `cursor agent --version` — IDE-bundled CLI (subcommand style)
|
|
1067
|
+
* 2. `agent --version` — standalone install via cursor.com/install (direct style)
|
|
998
1068
|
* Result is cached after first resolution.
|
|
999
1069
|
*/
|
|
1000
|
-
async
|
|
1001
|
-
if (this.
|
|
1070
|
+
async resolveStyle() {
|
|
1071
|
+
if (this.resolvedStyle !== null) return this.resolvedStyle;
|
|
1002
1072
|
try {
|
|
1003
1073
|
await execa("cursor", ["agent", "--version"], {
|
|
1004
1074
|
stdin: "ignore",
|
|
1005
1075
|
timeout: 2e3
|
|
1006
1076
|
});
|
|
1007
|
-
this.
|
|
1008
|
-
return "
|
|
1077
|
+
this.resolvedStyle = "subcommand";
|
|
1078
|
+
return "subcommand";
|
|
1009
1079
|
} catch {}
|
|
1010
1080
|
try {
|
|
1011
|
-
await execa("
|
|
1081
|
+
await execa("agent", ["--version"], {
|
|
1012
1082
|
stdin: "ignore",
|
|
1013
1083
|
timeout: 2e3
|
|
1014
1084
|
});
|
|
1015
|
-
this.
|
|
1016
|
-
return "
|
|
1085
|
+
this.resolvedStyle = "direct";
|
|
1086
|
+
return "direct";
|
|
1017
1087
|
} catch {}
|
|
1018
1088
|
return null;
|
|
1019
1089
|
}
|
|
1020
1090
|
async buildCommand(message) {
|
|
1021
|
-
const
|
|
1091
|
+
const style = await this.resolveStyle();
|
|
1022
1092
|
const agentArgs = [
|
|
1023
1093
|
"-p",
|
|
1024
1094
|
"--force",
|
|
@@ -1027,16 +1097,165 @@ var CursorBackend = class {
|
|
|
1027
1097
|
message
|
|
1028
1098
|
];
|
|
1029
1099
|
if (this.options.model) agentArgs.push("--model", this.options.model);
|
|
1030
|
-
if (
|
|
1100
|
+
if (!style) throw new Error("cursor agent CLI not found. Install via: curl -fsS https://cursor.com/install | bash");
|
|
1101
|
+
if (style === "direct") return {
|
|
1102
|
+
command: "agent",
|
|
1103
|
+
args: agentArgs
|
|
1104
|
+
};
|
|
1105
|
+
return {
|
|
1031
1106
|
command: "cursor",
|
|
1032
1107
|
args: ["agent", ...agentArgs]
|
|
1033
1108
|
};
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
|
|
1112
|
+
//#endregion
|
|
1113
|
+
//#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
|
+
var OpenCodeBackend = class {
|
|
1125
|
+
type = "opencode";
|
|
1126
|
+
options;
|
|
1127
|
+
constructor(options = {}) {
|
|
1128
|
+
this.options = {
|
|
1129
|
+
timeout: DEFAULT_IDLE_TIMEOUT,
|
|
1130
|
+
...options
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
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
|
+
async send(message, _options) {
|
|
1157
|
+
const args = this.buildArgs(message);
|
|
1158
|
+
const cwd = this.options.workspace || this.options.cwd;
|
|
1159
|
+
const timeout = this.options.timeout ?? DEFAULT_IDLE_TIMEOUT;
|
|
1160
|
+
try {
|
|
1161
|
+
const { stdout } = await execWithIdleTimeout({
|
|
1162
|
+
command: "opencode",
|
|
1163
|
+
args,
|
|
1164
|
+
cwd,
|
|
1165
|
+
timeout,
|
|
1166
|
+
onStdout: this.options.streamCallbacks ? createStreamParser(this.options.streamCallbacks, "OpenCode", opencodeAdapter) : void 0
|
|
1167
|
+
});
|
|
1168
|
+
return extractOpenCodeResult(stdout);
|
|
1169
|
+
} catch (error) {
|
|
1170
|
+
if (error instanceof IdleTimeoutError) throw new Error(`opencode timed out after ${timeout}ms of inactivity`);
|
|
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;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
async isAvailable() {
|
|
1179
|
+
try {
|
|
1180
|
+
await execa("opencode", ["--version"], {
|
|
1181
|
+
stdin: "ignore",
|
|
1182
|
+
timeout: 5e3
|
|
1183
|
+
});
|
|
1184
|
+
return true;
|
|
1185
|
+
} catch {
|
|
1186
|
+
return false;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
getInfo() {
|
|
1034
1190
|
return {
|
|
1035
|
-
|
|
1036
|
-
|
|
1191
|
+
name: "OpenCode CLI",
|
|
1192
|
+
model: this.options.model
|
|
1037
1193
|
};
|
|
1038
1194
|
}
|
|
1195
|
+
buildArgs(message) {
|
|
1196
|
+
const args = [
|
|
1197
|
+
"run",
|
|
1198
|
+
"--format",
|
|
1199
|
+
"json",
|
|
1200
|
+
message
|
|
1201
|
+
];
|
|
1202
|
+
if (this.options.model) args.push("--model", this.options.model);
|
|
1203
|
+
return args;
|
|
1204
|
+
}
|
|
1039
1205
|
};
|
|
1206
|
+
/**
|
|
1207
|
+
* Adapter for OpenCode --format json output.
|
|
1208
|
+
*
|
|
1209
|
+
* Events:
|
|
1210
|
+
* { type: "step_start", sessionID: "..." }
|
|
1211
|
+
* { type: "tool_use", part: { tool: "bash", state: { input: {...} } } }
|
|
1212
|
+
* { type: "text", part: { text: "..." } } → skipped (result only)
|
|
1213
|
+
* { type: "step_finish", part: { cost, tokens } }
|
|
1214
|
+
*/
|
|
1215
|
+
const opencodeAdapter = (raw) => {
|
|
1216
|
+
const event = raw;
|
|
1217
|
+
if (event.type === "step_start") return {
|
|
1218
|
+
kind: "init",
|
|
1219
|
+
sessionId: event.sessionID
|
|
1220
|
+
};
|
|
1221
|
+
if (event.type === "tool_use") {
|
|
1222
|
+
const { tool, state } = event.part;
|
|
1223
|
+
const args = state.input ? JSON.stringify(state.input) : "";
|
|
1224
|
+
return {
|
|
1225
|
+
kind: "tool_call",
|
|
1226
|
+
name: tool,
|
|
1227
|
+
args: args.length > 100 ? args.slice(0, 100) + "..." : args
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
if (event.type === "text") return { kind: "skip" };
|
|
1231
|
+
if (event.type === "step_finish") {
|
|
1232
|
+
const { cost, tokens } = event.part;
|
|
1233
|
+
return {
|
|
1234
|
+
kind: "completed",
|
|
1235
|
+
costUsd: cost,
|
|
1236
|
+
usage: tokens ? {
|
|
1237
|
+
input: tokens.input,
|
|
1238
|
+
output: tokens.output
|
|
1239
|
+
} : void 0
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
return null;
|
|
1243
|
+
};
|
|
1244
|
+
/**
|
|
1245
|
+
* Extract final result from OpenCode --format json output.
|
|
1246
|
+
*
|
|
1247
|
+
* Priority:
|
|
1248
|
+
* 1. Last text event
|
|
1249
|
+
* 2. Raw stdout fallback
|
|
1250
|
+
*/
|
|
1251
|
+
function extractOpenCodeResult(stdout) {
|
|
1252
|
+
const lines = stdout.trim().split("\n");
|
|
1253
|
+
for (let i = lines.length - 1; i >= 0; i--) try {
|
|
1254
|
+
const event = JSON.parse(lines[i]);
|
|
1255
|
+
if (event.type === "text" && event.part?.text) return { content: event.part.text };
|
|
1256
|
+
} catch {}
|
|
1257
|
+
return { content: stdout.trim() };
|
|
1258
|
+
}
|
|
1040
1259
|
|
|
1041
1260
|
//#endregion
|
|
1042
1261
|
//#region src/backends/sdk.ts
|
|
@@ -1049,15 +1268,17 @@ var SdkBackend = class {
|
|
|
1049
1268
|
modelId;
|
|
1050
1269
|
model = null;
|
|
1051
1270
|
maxTokens;
|
|
1271
|
+
provider;
|
|
1052
1272
|
constructor(options) {
|
|
1053
1273
|
this.modelId = options.model;
|
|
1054
1274
|
this.maxTokens = options.maxTokens ?? 4096;
|
|
1055
|
-
|
|
1275
|
+
this.provider = options.provider;
|
|
1276
|
+
if (!this.provider) try {
|
|
1056
1277
|
this.model = createModel(this.modelId);
|
|
1057
1278
|
} catch {}
|
|
1058
1279
|
}
|
|
1059
1280
|
async send(message, options) {
|
|
1060
|
-
if (!this.model) this.model = await createModelAsync(this.modelId);
|
|
1281
|
+
if (!this.model) this.model = this.provider ? await createModelWithProvider(this.modelId, this.provider) : await createModelAsync(this.modelId);
|
|
1061
1282
|
const result = await generateText({
|
|
1062
1283
|
model: this.model,
|
|
1063
1284
|
system: options?.system,
|
|
@@ -1075,7 +1296,7 @@ var SdkBackend = class {
|
|
|
1075
1296
|
}
|
|
1076
1297
|
async isAvailable() {
|
|
1077
1298
|
try {
|
|
1078
|
-
if (!this.model) this.model = await createModelAsync(this.modelId);
|
|
1299
|
+
if (!this.model) this.model = this.provider ? await createModelWithProvider(this.modelId, this.provider) : await createModelAsync(this.modelId);
|
|
1079
1300
|
return true;
|
|
1080
1301
|
} catch {
|
|
1081
1302
|
return false;
|
|
@@ -1133,10 +1354,14 @@ function createBackend(config) {
|
|
|
1133
1354
|
};
|
|
1134
1355
|
const model = getModelForBackend(normalized.model, normalized.type);
|
|
1135
1356
|
switch (normalized.type) {
|
|
1136
|
-
case "default":
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1357
|
+
case "default": {
|
|
1358
|
+
const provider = normalized.provider;
|
|
1359
|
+
return new SdkBackend({
|
|
1360
|
+
model,
|
|
1361
|
+
maxTokens: normalized.maxTokens,
|
|
1362
|
+
provider
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1140
1365
|
case "claude": return new ClaudeCodeBackend({
|
|
1141
1366
|
...normalized.options,
|
|
1142
1367
|
model
|
|
@@ -1149,6 +1374,10 @@ function createBackend(config) {
|
|
|
1149
1374
|
...normalized.options,
|
|
1150
1375
|
model
|
|
1151
1376
|
});
|
|
1377
|
+
case "opencode": return new OpenCodeBackend({
|
|
1378
|
+
...normalized.options,
|
|
1379
|
+
model
|
|
1380
|
+
});
|
|
1152
1381
|
default: throw new Error(`Unknown backend type: ${normalized.type}`);
|
|
1153
1382
|
}
|
|
1154
1383
|
}
|
|
@@ -1163,16 +1392,19 @@ async function checkBackends() {
|
|
|
1163
1392
|
const claude = new ClaudeCodeBackend();
|
|
1164
1393
|
const codex = new CodexBackend();
|
|
1165
1394
|
const cursor = new CursorBackend();
|
|
1166
|
-
const
|
|
1395
|
+
const opencode = new OpenCodeBackend();
|
|
1396
|
+
const [claudeAvailable, codexAvailable, cursorAvailable, opencodeAvailable] = await Promise.all([
|
|
1167
1397
|
withTimeout(claude.isAvailable(), 3e3),
|
|
1168
1398
|
withTimeout(codex.isAvailable(), 3e3),
|
|
1169
|
-
withTimeout(cursor.isAvailable(), 3e3)
|
|
1399
|
+
withTimeout(cursor.isAvailable(), 3e3),
|
|
1400
|
+
withTimeout(opencode.isAvailable(), 3e3)
|
|
1170
1401
|
]);
|
|
1171
1402
|
return {
|
|
1172
1403
|
default: true,
|
|
1173
1404
|
claude: claudeAvailable,
|
|
1174
1405
|
codex: codexAvailable,
|
|
1175
1406
|
cursor: cursorAvailable,
|
|
1407
|
+
opencode: opencodeAvailable,
|
|
1176
1408
|
mock: true
|
|
1177
1409
|
};
|
|
1178
1410
|
}
|
|
@@ -1201,9 +1433,14 @@ async function listBackends() {
|
|
|
1201
1433
|
type: "cursor",
|
|
1202
1434
|
available: availability.cursor,
|
|
1203
1435
|
name: "Cursor Agent CLI"
|
|
1436
|
+
},
|
|
1437
|
+
{
|
|
1438
|
+
type: "opencode",
|
|
1439
|
+
available: availability.opencode,
|
|
1440
|
+
name: "OpenCode CLI"
|
|
1204
1441
|
}
|
|
1205
1442
|
];
|
|
1206
1443
|
}
|
|
1207
1444
|
|
|
1208
1445
|
//#endregion
|
|
1209
|
-
export {
|
|
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 };
|