orquesta-cli 0.2.84 → 0.2.86

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/cli.js CHANGED
@@ -41,6 +41,36 @@ async function readPromptFromStdin() {
41
41
  }
42
42
  return Buffer.concat(chunks).toString('utf-8').trim();
43
43
  }
44
+ async function ensureBatutaFromEnv() {
45
+ const token = process.env['ORQUESTA_TOKEN'];
46
+ if (!token)
47
+ return;
48
+ const apiUrl = (process.env['ORQUESTA_API_URL'] || 'https://getorquesta.com').replace(/\/+$/, '');
49
+ if (configManager.getAllEndpoints().some((e) => e.id === 'batuta-proxy')) {
50
+ await configManager.removeEndpoint('batuta-proxy');
51
+ }
52
+ await configManager.addEndpoint({
53
+ id: 'batuta-proxy',
54
+ name: 'Batuta',
55
+ baseUrl: `${apiUrl}/api/v1`,
56
+ apiKey: token,
57
+ provider: 'batuta',
58
+ models: [{
59
+ id: 'batuta-auto',
60
+ name: 'Batuta Auto (smart routing)',
61
+ maxTokens: 200000,
62
+ enabled: true,
63
+ healthStatus: 'healthy',
64
+ lastHealthCheck: new Date(),
65
+ }],
66
+ createdAt: new Date(),
67
+ updatedAt: new Date(),
68
+ });
69
+ if (!configManager.getCurrentEndpoint()) {
70
+ await configManager.setCurrentEndpoint('batuta-proxy');
71
+ await configManager.setCurrentModel('batuta-auto');
72
+ }
73
+ }
44
74
  async function resolveHookToken(explicitToken) {
45
75
  await configManager.initialize();
46
76
  const saved = configManager.getOrquestaConfig();
@@ -129,6 +159,7 @@ program
129
159
  return;
130
160
  }
131
161
  await configManager.initialize();
162
+ await ensureBatutaFromEnv();
132
163
  if (shouldShowOnboarding()) {
133
164
  await runOnboarding();
134
165
  }
@@ -43,6 +43,7 @@ export declare class LLMClient {
43
43
  private apiKey;
44
44
  private model;
45
45
  private modelName;
46
+ private triedBatutaFallback;
46
47
  private currentAbortController;
47
48
  private isInterrupted;
48
49
  onStreamingContent: ((token: string) => void) | null;
@@ -54,6 +55,8 @@ export declare class LLMClient {
54
55
  checkInterrupted(): boolean;
55
56
  resetInterrupt(): void;
56
57
  isRequestActive(): boolean;
58
+ private isConnectionError;
59
+ private switchToBatutaProxy;
57
60
  private isRetryableError;
58
61
  private sleep;
59
62
  chatCompletionStream(options: Partial<LLMRequestOptions>): AsyncGenerator<LLMStreamChunk, void, unknown>;
@@ -106,6 +106,7 @@ export class LLMClient {
106
106
  apiKey;
107
107
  model;
108
108
  modelName;
109
+ triedBatutaFallback = false;
109
110
  currentAbortController = null;
110
111
  isInterrupted = false;
111
112
  onStreamingContent = null;
@@ -407,6 +408,11 @@ export class LLMClient {
407
408
  currentAttempt: currentAttempt + 1,
408
409
  });
409
410
  }
411
+ if (!this.triedBatutaFallback && this.isConnectionError(error) && this.switchToBatutaProxy()) {
412
+ this.triedBatutaFallback = true;
413
+ logger.flow('Primary endpoint unreachable — falling back to Batuta');
414
+ return this.chatCompletion(options, { maxRetries, currentAttempt: 1 });
415
+ }
410
416
  logger.flow('API call failed - Error handling');
411
417
  if (currentAttempt > 1) {
412
418
  logger.error(`LLM call final failure after ${maxRetries} retries`, {
@@ -439,6 +445,33 @@ export class LLMClient {
439
445
  isRequestActive() {
440
446
  return this.currentAbortController !== null;
441
447
  }
448
+ isConnectionError(error) {
449
+ if (!axios.isAxiosError(error))
450
+ return false;
451
+ const e = error;
452
+ if (e.response)
453
+ return false;
454
+ const codes = ['ECONNREFUSED', 'ETIMEDOUT', 'ECONNRESET', 'ECONNABORTED', 'ENOTFOUND', 'EHOSTUNREACH'];
455
+ return (e.code ? codes.includes(e.code) : false) || /timeout|network/i.test(e.message || '');
456
+ }
457
+ switchToBatutaProxy() {
458
+ const ep = configManager.getAllEndpoints().find(e => e.id === 'batuta-proxy' || e.provider === 'batuta');
459
+ if (!ep || !ep.apiKey || ep.baseUrl === this.baseUrl)
460
+ return false;
461
+ const model = ep.models?.find(m => m.enabled) || ep.models?.[0];
462
+ if (!model)
463
+ return false;
464
+ this.baseUrl = ep.baseUrl;
465
+ this.apiKey = ep.apiKey;
466
+ this.model = model.id;
467
+ this.modelName = model.name;
468
+ const providerDef = ep.provider ? getProvider(ep.provider) : undefined;
469
+ const headers = providerDef
470
+ ? buildAuthHeaders(providerDef, this.apiKey)
471
+ : { 'Content-Type': 'application/json', ...(this.apiKey && { Authorization: `Bearer ${this.apiKey}` }) };
472
+ this.axiosInstance = axios.create({ baseURL: this.baseUrl, headers, timeout: 600000 });
473
+ return true;
474
+ }
442
475
  isRetryableError(error) {
443
476
  if (error instanceof Error && error.message === 'INTERRUPTED') {
444
477
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orquesta-cli",
3
- "version": "0.2.84",
3
+ "version": "0.2.86",
4
4
  "description": "Orquesta CLI - AI-powered coding assistant with team collaboration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",