@orchagent/cli 0.2.25 → 0.2.26
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/commands/run.js +90 -7
- package/dist/lib/api.js +37 -2
- package/dist/lib/llm.js +44 -6
- package/package.json +1 -1
package/dist/commands/run.js
CHANGED
|
@@ -234,6 +234,58 @@ async function downloadDependenciesRecursively(config, depStatuses, visited = ne
|
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* Detect all available LLM providers from environment and server.
|
|
239
|
+
* Returns array of provider configs for fallback support.
|
|
240
|
+
*/
|
|
241
|
+
async function detectAllLlmKeys(supportedProviders, config) {
|
|
242
|
+
const providers = [];
|
|
243
|
+
const seen = new Set();
|
|
244
|
+
// Check environment variables for all providers
|
|
245
|
+
for (const provider of supportedProviders) {
|
|
246
|
+
if (provider === 'any') {
|
|
247
|
+
// Check all known providers
|
|
248
|
+
for (const [p, envVar] of Object.entries(llm_1.PROVIDER_ENV_VARS)) {
|
|
249
|
+
const key = process.env[envVar];
|
|
250
|
+
if (key && !seen.has(p)) {
|
|
251
|
+
seen.add(p);
|
|
252
|
+
providers.push({ provider: p, apiKey: key, model: (0, llm_1.getDefaultModel)(p) });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
const envVar = llm_1.PROVIDER_ENV_VARS[provider];
|
|
258
|
+
if (envVar) {
|
|
259
|
+
const key = process.env[envVar];
|
|
260
|
+
if (key && !seen.has(provider)) {
|
|
261
|
+
seen.add(provider);
|
|
262
|
+
providers.push({ provider, apiKey: key, model: (0, llm_1.getDefaultModel)(provider) });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Also check server keys if available
|
|
268
|
+
if (config?.apiKey) {
|
|
269
|
+
try {
|
|
270
|
+
const { fetchLlmKeys } = await Promise.resolve().then(() => __importStar(require('../lib/api')));
|
|
271
|
+
const serverKeys = await fetchLlmKeys(config);
|
|
272
|
+
for (const serverKey of serverKeys) {
|
|
273
|
+
if (!seen.has(serverKey.provider)) {
|
|
274
|
+
seen.add(serverKey.provider);
|
|
275
|
+
providers.push({
|
|
276
|
+
provider: serverKey.provider,
|
|
277
|
+
apiKey: serverKey.api_key,
|
|
278
|
+
model: serverKey.model || (0, llm_1.getDefaultModel)(serverKey.provider),
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
// Server fetch failed, continue with what we have
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return providers;
|
|
288
|
+
}
|
|
237
289
|
async function executePromptLocally(agentData, inputData, skillPrompts = [], config, providerOverride) {
|
|
238
290
|
// If provider override specified, validate and use only that provider
|
|
239
291
|
if (providerOverride) {
|
|
@@ -243,6 +295,44 @@ async function executePromptLocally(agentData, inputData, skillPrompts = [], con
|
|
|
243
295
|
const providersToCheck = providerOverride
|
|
244
296
|
? [providerOverride]
|
|
245
297
|
: agentData.supported_providers;
|
|
298
|
+
// Combine skill prompts with agent prompt (skills first, then agent)
|
|
299
|
+
let basePrompt = agentData.prompt || '';
|
|
300
|
+
if (skillPrompts.length > 0) {
|
|
301
|
+
basePrompt = [...skillPrompts, basePrompt].join('\n\n---\n\n');
|
|
302
|
+
}
|
|
303
|
+
// Build the prompt with input data (matches server behavior)
|
|
304
|
+
const prompt = (0, llm_1.buildPrompt)(basePrompt, inputData);
|
|
305
|
+
// When no provider override, detect all available providers for fallback support
|
|
306
|
+
if (!providerOverride) {
|
|
307
|
+
const allProviders = await detectAllLlmKeys(providersToCheck, config);
|
|
308
|
+
if (allProviders.length === 0) {
|
|
309
|
+
const providers = providersToCheck.join(', ');
|
|
310
|
+
throw new errors_1.CliError(`No LLM key found for: ${providers}\n` +
|
|
311
|
+
`Set an environment variable (e.g., OPENAI_API_KEY) or configure in web dashboard`);
|
|
312
|
+
}
|
|
313
|
+
// Apply agent default models to each provider config
|
|
314
|
+
const providersWithModels = allProviders.map((p) => ({
|
|
315
|
+
...p,
|
|
316
|
+
model: agentData.default_models?.[p.provider] || p.model,
|
|
317
|
+
}));
|
|
318
|
+
// Show which provider is being used (primary)
|
|
319
|
+
const primary = providersWithModels[0];
|
|
320
|
+
if (providersWithModels.length > 1) {
|
|
321
|
+
process.stderr.write(`Running with ${primary.provider} (${primary.model}), ` +
|
|
322
|
+
`${providersWithModels.length - 1} fallback(s) available...\n`);
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
process.stderr.write(`Running with ${primary.provider} (${primary.model})...\n`);
|
|
326
|
+
}
|
|
327
|
+
// Use fallback if multiple providers, otherwise single call
|
|
328
|
+
if (providersWithModels.length > 1) {
|
|
329
|
+
return await (0, llm_1.callLlmWithFallback)(providersWithModels, prompt, agentData.output_schema);
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
return await (0, llm_1.callLlm)(primary.provider, primary.apiKey, primary.model, prompt, agentData.output_schema);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// Provider override: use single provider (existing behavior)
|
|
246
336
|
const detected = await (0, llm_1.detectLlmKey)(providersToCheck, config);
|
|
247
337
|
if (!detected) {
|
|
248
338
|
const providers = providersToCheck.join(', ');
|
|
@@ -254,13 +344,6 @@ async function executePromptLocally(agentData, inputData, skillPrompts = [], con
|
|
|
254
344
|
const model = serverModel || agentData.default_models?.[provider] || (0, llm_1.getDefaultModel)(provider);
|
|
255
345
|
// Show which provider is being used (helpful for debugging rate limits)
|
|
256
346
|
process.stderr.write(`Running with ${provider} (${model})...\n`);
|
|
257
|
-
// Combine skill prompts with agent prompt (skills first, then agent)
|
|
258
|
-
let basePrompt = agentData.prompt || '';
|
|
259
|
-
if (skillPrompts.length > 0) {
|
|
260
|
-
basePrompt = [...skillPrompts, basePrompt].join('\n\n---\n\n');
|
|
261
|
-
}
|
|
262
|
-
// Build the prompt with input data (matches server behavior)
|
|
263
|
-
const prompt = (0, llm_1.buildPrompt)(basePrompt, inputData);
|
|
264
347
|
// Call the LLM directly
|
|
265
348
|
const response = await (0, llm_1.callLlm)(provider, key, model, prompt, agentData.output_schema);
|
|
266
349
|
return response;
|
package/dist/lib/api.js
CHANGED
|
@@ -59,6 +59,8 @@ exports.previewAgentVersion = previewAgentVersion;
|
|
|
59
59
|
exports.reportInstall = reportInstall;
|
|
60
60
|
const errors_1 = require("./errors");
|
|
61
61
|
const DEFAULT_TIMEOUT_MS = 15000;
|
|
62
|
+
const MAX_RETRIES = 3;
|
|
63
|
+
const BASE_DELAY_MS = 1000;
|
|
62
64
|
async function safeFetch(url, options) {
|
|
63
65
|
try {
|
|
64
66
|
return await fetch(url, {
|
|
@@ -74,6 +76,39 @@ async function safeFetch(url, options) {
|
|
|
74
76
|
throw new errors_1.NetworkError(url, err instanceof Error ? err : undefined);
|
|
75
77
|
}
|
|
76
78
|
}
|
|
79
|
+
async function safeFetchWithRetry(url, options) {
|
|
80
|
+
let lastError;
|
|
81
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
82
|
+
try {
|
|
83
|
+
const response = await safeFetch(url, options);
|
|
84
|
+
// Don't retry client errors (except 429)
|
|
85
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
|
|
86
|
+
return response;
|
|
87
|
+
}
|
|
88
|
+
// Retry on 5xx or 429
|
|
89
|
+
if (response.status >= 500 || response.status === 429) {
|
|
90
|
+
if (attempt < MAX_RETRIES) {
|
|
91
|
+
const delay = BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
92
|
+
const jitter = Math.random() * 500;
|
|
93
|
+
process.stderr.write(`Request failed (${response.status}), retrying in ${Math.round((delay + jitter) / 1000)}s...\n`);
|
|
94
|
+
await new Promise(r => setTimeout(r, delay + jitter));
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return response;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
lastError = error;
|
|
102
|
+
if (attempt < MAX_RETRIES) {
|
|
103
|
+
const delay = BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
104
|
+
const jitter = Math.random() * 500;
|
|
105
|
+
process.stderr.write(`Network error, retrying in ${Math.round((delay + jitter) / 1000)}s...\n`);
|
|
106
|
+
await new Promise(r => setTimeout(r, delay + jitter));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
throw lastError ?? new errors_1.NetworkError(url);
|
|
111
|
+
}
|
|
77
112
|
class ApiError extends Error {
|
|
78
113
|
status;
|
|
79
114
|
payload;
|
|
@@ -108,7 +143,7 @@ async function request(config, method, path, options = {}) {
|
|
|
108
143
|
if (!config.apiKey) {
|
|
109
144
|
throw new ApiError('Missing API key. Run `orchagent login` first.', 401);
|
|
110
145
|
}
|
|
111
|
-
const response = await
|
|
146
|
+
const response = await safeFetchWithRetry(buildUrl(config.apiUrl, path), {
|
|
112
147
|
method,
|
|
113
148
|
headers: {
|
|
114
149
|
Authorization: `Bearer ${config.apiKey}`,
|
|
@@ -122,7 +157,7 @@ async function request(config, method, path, options = {}) {
|
|
|
122
157
|
return (await response.json());
|
|
123
158
|
}
|
|
124
159
|
async function publicRequest(config, path) {
|
|
125
|
-
const response = await
|
|
160
|
+
const response = await safeFetchWithRetry(buildUrl(config.apiUrl, path));
|
|
126
161
|
if (!response.ok) {
|
|
127
162
|
throw await parseError(response);
|
|
128
163
|
}
|
package/dist/lib/llm.js
CHANGED
|
@@ -39,15 +39,28 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
39
39
|
};
|
|
40
40
|
})();
|
|
41
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
-
exports.DEFAULT_MODELS = exports.PROVIDER_ENV_VARS = void 0;
|
|
42
|
+
exports.DEFAULT_MODELS = exports.PROVIDER_ENV_VARS = exports.LlmError = void 0;
|
|
43
|
+
exports.isRateLimitError = isRateLimitError;
|
|
43
44
|
exports.detectLlmKeyFromEnv = detectLlmKeyFromEnv;
|
|
44
45
|
exports.detectLlmKey = detectLlmKey;
|
|
45
46
|
exports.getDefaultModel = getDefaultModel;
|
|
46
47
|
exports.buildPrompt = buildPrompt;
|
|
47
48
|
exports.callLlm = callLlm;
|
|
49
|
+
exports.callLlmWithFallback = callLlmWithFallback;
|
|
48
50
|
exports.validateProvider = validateProvider;
|
|
49
51
|
const errors_1 = require("./errors");
|
|
50
52
|
const llm_errors_1 = require("./llm-errors");
|
|
53
|
+
class LlmError extends errors_1.CliError {
|
|
54
|
+
statusCode;
|
|
55
|
+
constructor(message, statusCode) {
|
|
56
|
+
super(message);
|
|
57
|
+
this.statusCode = statusCode;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.LlmError = LlmError;
|
|
61
|
+
function isRateLimitError(error) {
|
|
62
|
+
return error instanceof LlmError && error.statusCode === 429;
|
|
63
|
+
}
|
|
51
64
|
// Environment variable names for each provider
|
|
52
65
|
exports.PROVIDER_ENV_VARS = {
|
|
53
66
|
openai: 'OPENAI_API_KEY',
|
|
@@ -169,6 +182,27 @@ async function callLlm(provider, apiKey, model, prompt, outputSchema) {
|
|
|
169
182
|
}
|
|
170
183
|
throw new errors_1.CliError(`Unsupported provider: ${provider}`);
|
|
171
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Call LLM with automatic fallback to next provider on rate limit.
|
|
187
|
+
* Tries each provider in order until one succeeds.
|
|
188
|
+
*/
|
|
189
|
+
async function callLlmWithFallback(providers, prompt, outputSchema) {
|
|
190
|
+
let lastError;
|
|
191
|
+
for (const { provider, apiKey, model } of providers) {
|
|
192
|
+
try {
|
|
193
|
+
return await callLlm(provider, apiKey, model, prompt, outputSchema);
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
lastError = error;
|
|
197
|
+
if (isRateLimitError(error)) {
|
|
198
|
+
process.stderr.write(`${provider} rate-limited, trying next provider...\n`);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
throw error; // Don't retry non-rate-limit errors
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
throw lastError ?? new errors_1.CliError('All LLM providers failed');
|
|
205
|
+
}
|
|
172
206
|
async function callOpenAI(apiKey, model, prompt, outputSchema) {
|
|
173
207
|
const body = {
|
|
174
208
|
model,
|
|
@@ -187,7 +221,8 @@ async function callOpenAI(apiKey, model, prompt, outputSchema) {
|
|
|
187
221
|
});
|
|
188
222
|
if (!response.ok) {
|
|
189
223
|
const text = await response.text();
|
|
190
|
-
|
|
224
|
+
const parsed = (0, llm_errors_1.parseLlmError)('openai', text, response.status);
|
|
225
|
+
throw new LlmError(parsed.message, response.status);
|
|
191
226
|
}
|
|
192
227
|
const data = (await response.json());
|
|
193
228
|
const content = data.choices?.[0]?.message?.content || '';
|
|
@@ -214,7 +249,8 @@ async function callAnthropic(apiKey, model, prompt, _outputSchema) {
|
|
|
214
249
|
});
|
|
215
250
|
if (!response.ok) {
|
|
216
251
|
const text = await response.text();
|
|
217
|
-
|
|
252
|
+
const parsed = (0, llm_errors_1.parseLlmError)('anthropic', text, response.status);
|
|
253
|
+
throw new LlmError(parsed.message, response.status);
|
|
218
254
|
}
|
|
219
255
|
const data = (await response.json());
|
|
220
256
|
const content = data.content?.[0]?.text || '';
|
|
@@ -236,7 +272,8 @@ async function callGemini(apiKey, model, prompt, _outputSchema) {
|
|
|
236
272
|
});
|
|
237
273
|
if (!response.ok) {
|
|
238
274
|
const text = await response.text();
|
|
239
|
-
|
|
275
|
+
const parsed = (0, llm_errors_1.parseLlmError)('gemini', text, response.status);
|
|
276
|
+
throw new LlmError(parsed.message, response.status);
|
|
240
277
|
}
|
|
241
278
|
const data = (await response.json());
|
|
242
279
|
const content = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
|
@@ -264,10 +301,11 @@ async function callOllama(endpoint, model, prompt, _outputSchema) {
|
|
|
264
301
|
});
|
|
265
302
|
if (!response.ok) {
|
|
266
303
|
if (response.status === 404) {
|
|
267
|
-
throw new
|
|
304
|
+
throw new LlmError(`Model '${model}' not found. Run: ollama pull ${model}`, 404);
|
|
268
305
|
}
|
|
269
306
|
const text = await response.text();
|
|
270
|
-
|
|
307
|
+
const parsed = (0, llm_errors_1.parseLlmError)('ollama', text, response.status);
|
|
308
|
+
throw new LlmError(parsed.message, response.status);
|
|
271
309
|
}
|
|
272
310
|
const data = await response.json();
|
|
273
311
|
const content = data.choices?.[0]?.message?.content || '';
|