adaptive-memory-multi-model-router 2.13.18 → 2.13.20

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.
Files changed (91) hide show
  1. package/.dockerignore +82 -0
  2. package/.env.example +303 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +83 -12
  4. package/.github/ISSUE_TEMPLATE/config.yml +12 -6
  5. package/.github/ISSUE_TEMPLATE/feature_request.md +61 -10
  6. package/.github/PULL_REQUEST_TEMPLATE.md +53 -26
  7. package/.github/dependabot.yml +9 -0
  8. package/.github/workflows/codeql.yml +38 -0
  9. package/.github/workflows/npm-publish.yml +20 -0
  10. package/.github/workflows/stale.yml +56 -0
  11. package/ARCHITECTURE.md +346 -0
  12. package/AUDIT_REPORT.md +28 -0
  13. package/CHANGELOG.md +386 -22
  14. package/CONTRIBUTORS.md +20 -0
  15. package/Dockerfile +53 -0
  16. package/Dockerfile.proxy +33 -0
  17. package/PR_STATUS_REPORT.md +148 -0
  18. package/README.md +22 -0
  19. package/RUNKIT.md +83 -0
  20. package/_schema.html +61 -15
  21. package/articles/AI_AGENT_LLM_ROUTING.md +150 -0
  22. package/articles/FROM_ZERO_TO_10K.md +107 -0
  23. package/articles/LLM_BENCHMARK_DEEP_DIVE.md +153 -0
  24. package/articles/TWEETS_10K_DOWNLOADS.md +47 -0
  25. package/articles/TWEETS_BENCHMARK_FIRST.md +46 -0
  26. package/articles/TWEETS_MCP_PLAY.md +51 -0
  27. package/articles/TWEETS_SEQUENTIAL_BROKEN.md +49 -0
  28. package/articles/TWEETS_WHY_BUILD.md +54 -0
  29. package/benchmark-results.json +26 -45
  30. package/cli/a3m +840 -0
  31. package/demo/package.json +13 -0
  32. package/demo/public/index.html +762 -0
  33. package/demo/server.js +405 -0
  34. package/dist/cli.js +4 -0
  35. package/docker-compose.yml +74 -0
  36. package/docs/.nojekyll +0 -0
  37. package/docs/BENCHMARK.md +96 -22
  38. package/docs/_config.yml +49 -0
  39. package/docs/api.html +513 -0
  40. package/docs/benchmark.html +387 -0
  41. package/docs/cli-cheatsheet.md +339 -0
  42. package/docs/comparison.md +108 -0
  43. package/docs/curl-examples.md +247 -0
  44. package/docs/index.html +390 -99
  45. package/docs/openapi.yaml +1318 -0
  46. package/docs/quick-start.html +366 -0
  47. package/docs/robots.txt +1 -1
  48. package/docs/sitemap.xml +23 -5
  49. package/docs/styles.css +682 -0
  50. package/examples/README.md +61 -0
  51. package/examples/a3m-sdk.js +124 -0
  52. package/examples/basic-route.js +54 -0
  53. package/examples/chat-loop.js +202 -0
  54. package/examples/classify-then-route.js +102 -0
  55. package/examples/cost-compare.js +120 -0
  56. package/examples/ensemble.js +160 -0
  57. package/integrations/langchain/README.md +216 -0
  58. package/integrations/langchain/a3m_langchain.ts +1360 -0
  59. package/integrations/langchain/example.ts +287 -0
  60. package/integrations/vercel-ai-sdk/README.md +49 -0
  61. package/integrations/vercel-ai-sdk/a3m_provider.ts +78 -0
  62. package/integrations/vercel-ai-sdk/example.ts +25 -0
  63. package/llms-full.txt +43 -0
  64. package/llms.txt +9 -0
  65. package/mcp-server/README.md +188 -0
  66. package/mcp-server/package.json +29 -0
  67. package/mcp-server/src/index.ts +744 -0
  68. package/mcp-server/tsconfig.json +19 -0
  69. package/package.json +3 -3
  70. package/proxy/README.md +227 -0
  71. package/proxy/package-lock.json +831 -0
  72. package/proxy/package.json +17 -0
  73. package/proxy/rate-limit.js +145 -0
  74. package/proxy/rate-limit.test.js +311 -0
  75. package/proxy/server.js +970 -0
  76. package/scripts/banner.js +29 -0
  77. package/scripts/compare-providers.sh +230 -0
  78. package/scripts/cross_post.py +443 -0
  79. package/scripts/publish_fcc.py +106 -0
  80. package/scripts/push-to-gitee.sh +52 -0
  81. package/src/tui/dashboard.ts +13 -0
  82. package/tests/__mocks__/tokenUtils.ts +22 -0
  83. package/tests/memory/episodicMemory.test.ts +227 -0
  84. package/tests/package-lock.json +1628 -0
  85. package/tests/package.json +18 -0
  86. package/tests/routing/ensembleVoting.test.ts +236 -0
  87. package/tests/routing/providerRetry.test.ts +360 -0
  88. package/tests/routing/queryTypePresets.test.ts +206 -0
  89. package/tests/tsconfig.json +21 -0
  90. package/tests/vitest.config.ts +18 -0
  91. package/.env +0 -2
package/cli/a3m ADDED
@@ -0,0 +1,840 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * A3M Router — Standalone CLI
4
+ * =============================
5
+ * Zero dependencies. Just: chmod +x a3m && ./a3m "your query"
6
+ *
7
+ * Features:
8
+ * - 14 built-in providers (auto-detects API keys from env)
9
+ * - Parallel ensemble mode (run N providers simultaneously)
10
+ * - Automatic fallback (try A -> B -> C on failure)
11
+ * - Rich ANSI output without chalk
12
+ * - Single file, no npm install required
13
+ *
14
+ * Usage:
15
+ * ./a3m "what is the capital of France?"
16
+ * ./a3m --provider groq "tell me a joke"
17
+ * ./a3m --ensemble "compare responses across providers"
18
+ * ./a3m --fallback "try providers until one works"
19
+ * ./a3m --list-providers
20
+ * ./a3m --help
21
+ */
22
+
23
+ // =====================================================================
24
+ // ANSI COLORS (no chalk dependency)
25
+ // =====================================================================
26
+ const C = {
27
+ reset: '\x1b[0m',
28
+ bold: '\x1b[1m',
29
+ dim: '\x1b[2m',
30
+ italic: '\x1b[3m',
31
+ ul: '\x1b[4m',
32
+
33
+ red: '\x1b[31m',
34
+ green: '\x1b[32m',
35
+ yellow: '\x1b[33m',
36
+ blue: '\x1b[34m',
37
+ magenta: '\x1b[35m',
38
+ cyan: '\x1b[36m',
39
+ white: '\x1b[37m',
40
+
41
+ bgRed: '\x1b[41m',
42
+ bgGreen: '\x1b[42m',
43
+ bgYellow: '\x1b[43m',
44
+ bgBlue: '\x1b[44m',
45
+ bgMagenta: '\x1b[45m',
46
+ bgCyan: '\x1b[46m',
47
+
48
+ // Bright variants
49
+ brightRed: '\x1b[91m',
50
+ brightGreen: '\x1b[92m',
51
+ brightYellow: '\x1b[93m',
52
+ brightBlue: '\x1b[94m',
53
+ brightMagenta: '\x1b[95m',
54
+ brightCyan: '\x1b[96m',
55
+ };
56
+
57
+ function c(code, text) {
58
+ return code + text + C.reset;
59
+ }
60
+
61
+ function colorize(providerName, text) {
62
+ const colors = [C.cyan, C.green, C.yellow, C.magenta, C.brightBlue, C.brightCyan, C.brightGreen, C.brightMagenta];
63
+ let hash = 0;
64
+ for (let i = 0; i < providerName.length; i++) hash = ((hash << 5) - hash) + providerName.charCodeAt(i);
65
+ return colors[Math.abs(hash) % colors.length] + text + C.reset;
66
+ }
67
+
68
+ // =====================================================================
69
+ // PROVIDER DEFINITIONS
70
+ // =====================================================================
71
+ // Each provider has:
72
+ // - id: short name for --provider flag
73
+ // - name: human-readable
74
+ // - envKey: env var that holds the API key
75
+ // - baseUrl: API endpoint
76
+ // - model: default model
77
+ // - format: 'openai' (standard chat completions) | 'anthropic' | 'google'
78
+ // - headers(): returns {Authorization, Content-Type} etc.
79
+ // - parseResponse(json): extracts {content, model, usage}
80
+ // =====================================================================
81
+
82
+ function getEnv(key, fallback) {
83
+ // Also checks .env file in cwd
84
+ const val = process.env[key];
85
+ if (val && val.length > 0 && val !== '...' && val !== 'sk-...' && val !== 'gsk_...' && val !== 'nvapi-...') return val;
86
+ return fallback || '';
87
+ }
88
+
89
+ function makeProvider(id, name, envKey, baseUrl, model, opts) {
90
+ opts = opts || {};
91
+ return {
92
+ id,
93
+ name,
94
+ envKey,
95
+ baseUrl,
96
+ model: opts.model || model,
97
+ format: opts.format || 'openai',
98
+ maxTokens: opts.maxTokens || 2048,
99
+ priority: opts.priority || 50,
100
+ tier: opts.tier || 'unknown',
101
+ costPerK: opts.costPerK || { input: 0, output: 0 },
102
+ getApiKey() {
103
+ return getEnv(this.envKey);
104
+ },
105
+ isAvailable() {
106
+ return !!this.getApiKey();
107
+ },
108
+ buildBody(prompt, systemPrompt) {
109
+ const msgs = [];
110
+ if (systemPrompt) msgs.push({ role: 'system', content: systemPrompt });
111
+ msgs.push({ role: 'user', content: prompt });
112
+ return { model: this.model, messages: msgs, max_tokens: this.maxTokens };
113
+ },
114
+ buildHeaders() {
115
+ return {
116
+ 'Authorization': 'Bearer ' + this.getApiKey(),
117
+ 'Content-Type': 'application/json',
118
+ };
119
+ },
120
+ parseResponse(json) {
121
+ if (json.error) throw new Error(json.error.message || JSON.stringify(json.error));
122
+ const choice = json.choices && json.choices[0];
123
+ if (!choice) throw new Error('No response choices returned');
124
+ return {
125
+ content: choice.message ? (choice.message.content || '') : '',
126
+ model: json.model || this.model,
127
+ usage: json.usage || {},
128
+ };
129
+ },
130
+ };
131
+ }
132
+
133
+ function makeGoogleProvider(id, name, envKey, baseUrl, model, opts) {
134
+ opts = opts || {};
135
+ const p = makeProvider(id, name, envKey, baseUrl, model, opts);
136
+ p.format = 'google';
137
+ p.buildHeaders = function () {
138
+ return { 'Content-Type': 'application/json' };
139
+ };
140
+ p.buildBody = function (prompt, systemPrompt) {
141
+ const contents = [];
142
+ if (systemPrompt) {
143
+ contents.push({ role: 'user', parts: [{ text: systemPrompt + '\n\n' + prompt }] });
144
+ } else {
145
+ contents.push({ role: 'user', parts: [{ text: prompt }] });
146
+ }
147
+ return {
148
+ contents,
149
+ generationConfig: { maxOutputTokens: this.maxTokens },
150
+ };
151
+ };
152
+ p.parseResponse = function (json) {
153
+ if (json.error) throw new Error(json.error.message || JSON.stringify(json.error));
154
+ const candidate = json.candidates && json.candidates[0];
155
+ if (!candidate) throw new Error('No candidates returned');
156
+ const parts = candidate.content && candidate.content.parts;
157
+ const text = parts ? parts.map(function (p) { return p.text || ''; }).join('') : '';
158
+ return {
159
+ content: text,
160
+ model: json.modelVersion || this.model,
161
+ usage: {},
162
+ };
163
+ };
164
+ return p;
165
+ }
166
+
167
+ function makeAnthropicProvider(id, name, envKey, baseUrl, model, opts) {
168
+ opts = opts || {};
169
+ const p = makeProvider(id, name, envKey, baseUrl, model, opts);
170
+ p.format = 'anthropic';
171
+ p.buildHeaders = function () {
172
+ return {
173
+ 'x-api-key': this.getApiKey(),
174
+ 'anthropic-version': '2023-06-01',
175
+ 'Content-Type': 'application/json',
176
+ };
177
+ };
178
+ p.buildBody = function (prompt, systemPrompt) {
179
+ return {
180
+ model: this.model,
181
+ max_tokens: this.maxTokens,
182
+ system: systemPrompt || '',
183
+ messages: [{ role: 'user', content: prompt }],
184
+ };
185
+ };
186
+ p.parseResponse = function (json) {
187
+ if (json.error) throw new Error(json.error.message || JSON.stringify(json.error));
188
+ return {
189
+ content: json.content && json.content[0] ? json.content[0].text : '',
190
+ model: json.model || this.model,
191
+ usage: json.usage || {},
192
+ };
193
+ };
194
+ return p;
195
+ }
196
+
197
+ function makeNvidiaProvider(id, name, envKey, baseUrl, model, opts) {
198
+ opts = opts || {};
199
+ const p = makeProvider(id, name, envKey, baseUrl, model, opts);
200
+ // NVIDIA uses nvapi- prefix but still Bearer auth
201
+ p.getApiKey = function () {
202
+ const val = getEnv('NVIDIA_API_KEY');
203
+ return val;
204
+ };
205
+ return p;
206
+ }
207
+
208
+ // =====================================================================
209
+ // ALL PROVIDERS
210
+ // =====================================================================
211
+
212
+ const PROVIDERS = [
213
+ // -- Fast inference (cheap/free) --
214
+ {
215
+ id: 'groq',
216
+ name: 'Groq',
217
+ envKey: 'GROQ_API_KEY',
218
+ baseUrl: 'https://api.groq.com/openai/v1/chat/completions',
219
+ model: 'llama-3.3-70b-versatile',
220
+ priority: 1,
221
+ tier: 'cheap',
222
+ costPerK: { input: 0.59, output: 0.79 },
223
+ },
224
+ {
225
+ id: 'cerebras',
226
+ name: 'Cerebras',
227
+ envKey: 'CEREBRAS_API_KEY',
228
+ baseUrl: 'https://api.cerebras.ai/v1/chat/completions',
229
+ model: 'llama-3.3-70b',
230
+ priority: 2,
231
+ tier: 'cheap',
232
+ costPerK: { input: 0.60, output: 0.60 },
233
+ },
234
+ {
235
+ id: 'deepinfra',
236
+ name: 'DeepInfra',
237
+ envKey: 'DEEPINFRA_API_KEY',
238
+ baseUrl: 'https://api.deepinfra.com/v1/openai/chat/completions',
239
+ model: 'meta-llama/Meta-Llama-3.1-70B-Instruct',
240
+ priority: 3,
241
+ tier: 'cheap',
242
+ costPerK: { input: 0.05, output: 0.05 },
243
+ },
244
+ {
245
+ id: 'together',
246
+ name: 'Together AI',
247
+ envKey: 'TOGETHER_API_KEY',
248
+ baseUrl: 'https://api.together.xyz/v1/chat/completions',
249
+ model: 'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo',
250
+ priority: 4,
251
+ tier: 'cheap',
252
+ costPerK: { input: 0.18, output: 0.18 },
253
+ },
254
+ {
255
+ id: 'fireworks',
256
+ name: 'Fireworks AI',
257
+ envKey: 'FIREWORKS_API_KEY',
258
+ baseUrl: 'https://api.fireworks.ai/inference/v1/chat/completions',
259
+ model: 'accounts/fireworks/models/llama-v3p1-70b-instruct',
260
+ priority: 5,
261
+ tier: 'cheap',
262
+ costPerK: { input: 0.20, output: 0.20 },
263
+ },
264
+
265
+ // -- Free tier providers --
266
+ {
267
+ id: 'nvidia',
268
+ name: 'NVIDIA NIM',
269
+ envKey: 'NVIDIA_API_KEY',
270
+ baseUrl: 'https://integrate.api.nvidia.com/v1/chat/completions',
271
+ model: 'meta/llama-3.3-70b-instruct',
272
+ priority: 6,
273
+ tier: 'free',
274
+ costPerK: { input: 0, output: 0 },
275
+ },
276
+ {
277
+ id: 'google',
278
+ name: 'Google Gemini',
279
+ envKey: 'GOOGLE_API_KEY',
280
+ baseUrl: 'https://generativelanguage.googleapis.com/v1beta/models/',
281
+ model: 'gemini-2.5-flash',
282
+ priority: 7,
283
+ tier: 'free',
284
+ format: 'google',
285
+ costPerK: { input: 0, output: 0 },
286
+ },
287
+ {
288
+ id: 'openrouter',
289
+ name: 'OpenRouter',
290
+ envKey: 'OPENROUTER_API_KEY',
291
+ baseUrl: 'https://openrouter.ai/api/v1/chat/completions',
292
+ model: 'openai/gpt-4o',
293
+ priority: 8,
294
+ tier: 'cheap',
295
+ costPerK: { input: 0, output: 0 },
296
+ },
297
+
298
+ // -- Mid tier --
299
+ {
300
+ id: 'mistral',
301
+ name: 'Mistral',
302
+ envKey: 'MISTRAL_API_KEY',
303
+ baseUrl: 'https://api.mistral.ai/v1/chat/completions',
304
+ model: 'mistral-large-latest',
305
+ priority: 9,
306
+ tier: 'mid',
307
+ costPerK: { input: 0.20, output: 0.60 },
308
+ },
309
+ {
310
+ id: 'deepseek',
311
+ name: 'DeepSeek',
312
+ envKey: 'DEEPSEEK_API_KEY',
313
+ baseUrl: 'https://api.deepseek.com/v1/chat/completions',
314
+ model: 'deepseek-v4-flash',
315
+ priority: 10,
316
+ tier: 'mid',
317
+ costPerK: { input: 0.14, output: 0.28 },
318
+ },
319
+ {
320
+ id: 'perplexity',
321
+ name: 'Perplexity',
322
+ envKey: 'PERPLEXITY_API_KEY',
323
+ baseUrl: 'https://api.perplexity.ai/chat/completions',
324
+ model: 'llama-3.1-sonar-large-128k-online',
325
+ priority: 11,
326
+ tier: 'mid',
327
+ costPerK: { input: 1.0, output: 1.0 },
328
+ },
329
+
330
+ // -- Premium --
331
+ {
332
+ id: 'openai',
333
+ name: 'OpenAI',
334
+ envKey: 'OPENAI_API_KEY',
335
+ baseUrl: 'https://api.openai.com/v1/chat/completions',
336
+ model: 'gpt-4o',
337
+ priority: 12,
338
+ tier: 'premium',
339
+ costPerK: { input: 2.50, output: 10.0 },
340
+ },
341
+ {
342
+ id: 'anthropic',
343
+ name: 'Anthropic',
344
+ envKey: 'ANTHROPIC_API_KEY',
345
+ baseUrl: 'https://api.anthropic.com/v1/messages',
346
+ model: 'claude-sonnet-4-20250514',
347
+ priority: 13,
348
+ tier: 'premium',
349
+ format: 'anthropic',
350
+ costPerK: { input: 3.0, output: 15.0 },
351
+ },
352
+ {
353
+ id: 'xai',
354
+ name: 'xAI Grok',
355
+ envKey: 'XAI_API_KEY',
356
+ baseUrl: 'https://api.x.ai/v1/chat/completions',
357
+ model: 'grok-3',
358
+ priority: 14,
359
+ tier: 'premium',
360
+ costPerK: { input: 3.0, output: 15.0 },
361
+ },
362
+ ];
363
+
364
+ // =====================================================================
365
+ // PROVIDER FACTORY
366
+ // =====================================================================
367
+
368
+ function buildProvider(cfg) {
369
+ if (cfg.format === 'google') {
370
+ const url = cfg.baseUrl + cfg.model + ':generateContent?key=' + getEnv(cfg.envKey);
371
+ return makeGoogleProvider(cfg.id, cfg.name, cfg.envKey, url, cfg.model, cfg);
372
+ }
373
+ if (cfg.format === 'anthropic') {
374
+ return makeAnthropicProvider(cfg.id, cfg.name, cfg.envKey, cfg.baseUrl, cfg.model, cfg);
375
+ }
376
+ if (cfg.id === 'nvidia') {
377
+ return makeNvidiaProvider(cfg.id, cfg.name, cfg.envKey, cfg.baseUrl, cfg.model, cfg);
378
+ }
379
+ return makeProvider(cfg.id, cfg.name, cfg.envKey, cfg.baseUrl, cfg.model, cfg);
380
+ }
381
+
382
+ function getAvailableProviders() {
383
+ return PROVIDERS
384
+ .filter(function (p) { return buildProvider(p).isAvailable(); })
385
+ .sort(function (a, b) { return a.priority - b.priority; });
386
+ }
387
+
388
+ // =====================================================================
389
+ // HTTP REQUEST (no dependencies)
390
+ // =====================================================================
391
+
392
+ function httpsRequest(url, method, headers, body, timeout) {
393
+ return new Promise(function (resolve, reject) {
394
+ timeout = timeout || 30000;
395
+ var u = new URL(url);
396
+ var mod = u.protocol === 'https:' ? require('https') : require('http');
397
+
398
+ var options = {
399
+ hostname: u.hostname,
400
+ port: u.port || (u.protocol === 'https:' ? 443 : 80),
401
+ path: u.pathname + u.search,
402
+ method: method || 'GET',
403
+ headers: headers || {},
404
+ timeout: timeout,
405
+ };
406
+
407
+ var req = mod.request(options, function (res) {
408
+ var data = '';
409
+ res.on('data', function (chunk) { data += chunk; });
410
+ res.on('end', function () {
411
+ try {
412
+ var parsed = JSON.parse(data);
413
+ resolve({ status: res.statusCode, headers: res.headers, data: parsed, raw: data });
414
+ } catch (e) {
415
+ reject(new Error('Invalid JSON response (' + res.statusCode + '): ' + data.substring(0, 200)));
416
+ }
417
+ });
418
+ });
419
+
420
+ req.on('error', function (e) { reject(e); });
421
+ req.on('timeout', function () { req.destroy(); reject(new Error('Request timed out after ' + timeout + 'ms')); });
422
+
423
+ if (body) {
424
+ req.write(typeof body === 'string' ? body : JSON.stringify(body));
425
+ }
426
+ req.end();
427
+ });
428
+ }
429
+
430
+ // =====================================================================
431
+ // CALL A SINGLE PROVIDER
432
+ // =====================================================================
433
+
434
+ async function callProvider(providerCfg, prompt, systemPrompt) {
435
+ var provider = buildProvider(providerCfg);
436
+ var startTime = Date.now();
437
+
438
+ var body = provider.buildBody(prompt, systemPrompt);
439
+ var headers = provider.buildHeaders();
440
+
441
+ var resp = await httpsRequest(provider.baseUrl, 'POST', headers, body, 60000);
442
+
443
+ if (resp.status >= 400) {
444
+ var errMsg = resp.data && resp.data.error ? (resp.data.error.message || JSON.stringify(resp.data.error)) : ('HTTP ' + resp.status);
445
+ throw new Error(errMsg);
446
+ }
447
+
448
+ var parsed = provider.parseResponse(resp.data);
449
+ var latency = Date.now() - startTime;
450
+
451
+ var usage = parsed.usage || {};
452
+ var promptTokens = usage.prompt_tokens || 0;
453
+ var completionTokens = usage.completion_tokens || 0;
454
+ var totalTokens = usage.total_tokens || (promptTokens + completionTokens) || estimateTokens(prompt) + estimateTokens(parsed.content);
455
+
456
+ var cost = (promptTokens / 1000000 * providerCfg.costPerK.input) +
457
+ (completionTokens / 1000000 * providerCfg.costPerK.output);
458
+
459
+ return {
460
+ provider: providerCfg.name,
461
+ model: parsed.model,
462
+ content: parsed.content || '',
463
+ latency: latency,
464
+ totalTokens: totalTokens,
465
+ cost: cost,
466
+ usage: usage,
467
+ success: true,
468
+ };
469
+ }
470
+
471
+ // =====================================================================
472
+ // TOKEN ESTIMATION (rough)
473
+ // =====================================================================
474
+
475
+ function estimateTokens(text) {
476
+ if (!text) return 0;
477
+ // Rough: ~4 chars per token for English
478
+ return Math.ceil(text.length / 4);
479
+ }
480
+
481
+ // =====================================================================
482
+ // FALLBACK MODE (try providers in priority order)
483
+ // =====================================================================
484
+
485
+ async function withFallback(prompt, opts) {
486
+ opts = opts || {};
487
+ var available = getAvailableProviders();
488
+ if (opts.providerFilter) {
489
+ available = available.filter(function (p) { return opts.providerFilter === p.id; });
490
+ }
491
+ if (available.length === 0) {
492
+ return { success: false, error: 'No providers available. Set at least one API key.', errors: [] };
493
+ }
494
+
495
+ var errors = [];
496
+ var systemPrompt = opts.systemPrompt || '';
497
+
498
+ // If specific provider requested, try just that one first
499
+ if (opts.providerFilter) {
500
+ var target = available[0];
501
+ try {
502
+ var result = await callProvider(target, prompt, systemPrompt);
503
+ return result;
504
+ } catch (e) {
505
+ errors.push({ provider: target.name, error: e.message });
506
+ return { success: false, error: e.message, errors: errors, provider: target.name };
507
+ }
508
+ }
509
+
510
+ // Try each provider in priority order
511
+ for (var i = 0; i < available.length; i++) {
512
+ var p = available[i];
513
+ try {
514
+ var result = await callProvider(p, prompt, systemPrompt);
515
+ result.fallbackAttempts = i;
516
+ return result;
517
+ } catch (e) {
518
+ var emsg = e.message.substring(0, 200);
519
+ errors.push({ provider: p.name, error: emsg });
520
+ // Continue to next provider
521
+ }
522
+ }
523
+
524
+ return { success: false, error: 'All providers failed', errors: errors };
525
+ }
526
+
527
+ // =====================================================================
528
+ // ENSEMBLE MODE (run N providers in parallel, merge results)
529
+ // =====================================================================
530
+
531
+ async function runEnsemble(prompt, opts) {
532
+ opts = opts || {};
533
+ var available = getAvailableProviders();
534
+ var systemPrompt = opts.systemPrompt || '';
535
+
536
+ // Limit to max providers
537
+ var maxProviders = opts.maxProviders || 5;
538
+ var selected = available.slice(0, maxProviders);
539
+
540
+ if (selected.length === 0) {
541
+ return { success: false, error: 'No providers available.', results: [] };
542
+ }
543
+
544
+ // Fire all requests in parallel
545
+ var results = await Promise.allSettled(
546
+ selected.map(function (p) {
547
+ return callProvider(p, prompt, systemPrompt);
548
+ })
549
+ );
550
+
551
+ var successes = [];
552
+ var failures = [];
553
+
554
+ for (var i = 0; i < results.length; i++) {
555
+ var r = results[i];
556
+ if (r.status === 'fulfilled') {
557
+ successes.push(r.value);
558
+ } else {
559
+ failures.push({ provider: selected[i].name, error: r.reason.message || String(r.reason) });
560
+ }
561
+ }
562
+
563
+ return {
564
+ success: successes.length > 0,
565
+ totalResults: successes.length,
566
+ totalFailures: failures.length,
567
+ results: successes,
568
+ failures: failures,
569
+ };
570
+ }
571
+
572
+ // =====================================================================
573
+ // OUTPUT FORMATTING
574
+ // =====================================================================
575
+
576
+ function printBanner() {
577
+ var banner = '';
578
+ banner += c(C.cyan, ' ╔═══════════════════════════════════════════════╗\n');
579
+ banner += c(C.cyan, ' ║') + c(C.bold + C.brightCyan, ' A3M Router — Adaptive Multi-Model Router ') + c(C.dim + C.cyan, ' v2.13 ') + c(C.cyan, ' ║\n');
580
+ banner += c(C.cyan, ' ║') + c(C.dim + C.cyan, ' Parallel multi-LLM execution · 14 providers ') + c(C.cyan, ' ║\n');
581
+ banner += c(C.cyan, ' ╚═══════════════════════════════════════════════╝\n');
582
+ console.log(banner);
583
+ }
584
+
585
+ function printHelp() {
586
+ printBanner();
587
+ console.log(c(C.bold, ' USAGE:'));
588
+ console.log(' ./a3m ' + c(C.cyan, '"your query"') + ' Route query to best available provider');
589
+ console.log(' ./a3m ' + c(C.green, '--provider groq') + ' ' + c(C.cyan, '"query"') + ' Use a specific provider');
590
+ console.log(' ./a3m ' + c(C.yellow, '--ensemble') + ' ' + c(C.cyan, '"query"') + ' Run ALL providers in parallel, compare results');
591
+ console.log(' ./a3m ' + c(C.magenta, '--fallback') + ' ' + c(C.cyan, '"query"') + ' Try providers in priority order until one works');
592
+ console.log(' ./a3m ' + c(C.brightBlue, '--list-providers') + ' Show all providers and their status');
593
+ console.log(' ./a3m ' + c(C.blue, '--help') + ' Show this help');
594
+ console.log('');
595
+ console.log(c(C.bold, ' EXAMPLES:'));
596
+ console.log(' ./a3m "explain quantum computing"');
597
+ console.log(' ./a3m --provider groq "write a poem"');
598
+ console.log(' ./a3m --ensemble "compare these models for me"');
599
+ console.log(' ./a3m --fallback "try until one works"');
600
+ console.log('');
601
+ console.log(c(C.bold, ' CONFIGURATION:'));
602
+ console.log(' Set any of these env vars (or in .env file):');
603
+ console.log(' ' + c(C.dim, 'GROQ_API_KEY CEREBRAS_API_KEY MISTRAL_API_KEY'));
604
+ console.log(' ' + c(C.dim, 'NVIDIA_API_KEY GOOGLE_API_KEY OPENAI_API_KEY'));
605
+ console.log(' ' + c(C.dim, 'ANTHROPIC_API_KEY DEEPSEEK_API_KEY DEEPINFRA_API_KEY'));
606
+ console.log(' ' + c(C.dim, 'TOGETHER_API_KEY PERPLEXITY_API_KEY XAI_API_KEY'));
607
+ console.log(' ' + c(C.dim, 'FIREWORKS_API_KEY OPENROUTER_API_KEY'));
608
+ console.log('');
609
+ process.exit(0);
610
+ }
611
+
612
+ function printProviders() {
613
+ printBanner();
614
+ console.log(c(C.bold, ' AVAILABLE PROVIDERS:\n'));
615
+
616
+ var allProviders = PROVIDERS;
617
+ var available = getAvailableProviders();
618
+ var availableIds = {};
619
+ for (var i = 0; i < available.length; i++) availableIds[available[i].id] = true;
620
+
621
+ for (var pi = 0; pi < allProviders.length; pi++) {
622
+ var p = allProviders[pi];
623
+ var avail = availableIds[p.id];
624
+ var statusStr = avail ? c(C.green, ' ✅') : c(C.dim + C.white, ' ❌');
625
+ var keyStr = avail ? c(C.green, ' ✅ KEY SET') : (function () {
626
+ // Check if env var exists at all
627
+ var val = process.env[p.envKey];
628
+ if (val) return c(C.yellow, ' ⚠ no key');
629
+ return c(C.dim, ' not set');
630
+ })();
631
+ var tierColor = p.tier === 'free' ? C.green : p.tier === 'cheap' ? C.cyan : p.tier === 'mid' ? C.yellow : C.magenta;
632
+ var modelStr = p.model.length > 35 ? p.model.substring(0, 33) + '..' : p.model;
633
+
634
+ console.log(
635
+ statusStr + ' ' +
636
+ c(C.bold, p.name.padEnd(18)) +
637
+ c(tierColor, ('[' + p.tier + ']').padEnd(8)) +
638
+ c(C.dim, modelStr.padEnd(38)) +
639
+ c(C.dim, '#' + p.envKey) + ' ' +
640
+ keyStr
641
+ );
642
+ }
643
+
644
+ console.log('\n ' + c(C.bold, String(available.length)) + ' of ' + allProviders.length + ' providers available\n');
645
+ process.exit(0);
646
+ }
647
+
648
+ function printResult(result, verbose) {
649
+ if (!result.success) {
650
+ console.log(c(C.red, ' ERROR: ') + result.error);
651
+ if (result.errors && result.errors.length > 0) {
652
+ console.log('');
653
+ for (var i = 0; i < result.errors.length; i++) {
654
+ var e = result.errors[i];
655
+ console.log(' ' + c(C.yellow, e.provider + ':') + ' ' + c(C.dim, e.error));
656
+ }
657
+ }
658
+ return;
659
+ }
660
+
661
+ var providerColor = colorize(result.provider, result.provider);
662
+
663
+ console.log('');
664
+ console.log(' ' + c(C.dim, '┌─') + c(C.bold, ' A3M Response ') + c(C.dim, '─────────────────────────────────────────────'));
665
+ console.log(' ' + c(C.dim, '│'));
666
+ console.log(' ' + c(C.dim, '│ Provider: ') + providerColor);
667
+ console.log(' ' + c(C.dim, '│ Model: ') + c(C.italic, result.model));
668
+ console.log(' ' + c(C.dim, '│ Latency: ') + c(C.cyan, result.latency + 'ms'));
669
+ console.log(' ' + c(C.dim, '│ Tokens: ') + c(C.yellow, String(result.totalTokens)));
670
+ console.log(' ' + c(C.dim, '│ Cost: ') + c(C.magenta, '$' + result.cost.toFixed(6)));
671
+ if (result.fallbackAttempts !== undefined && result.fallbackAttempts > 0) {
672
+ console.log(' ' + c(C.dim, '│ Fallback: ') + c(C.yellow, 'attempted ' + result.fallbackAttempts + ' provider(s) before success'));
673
+ }
674
+ console.log(' ' + c(C.dim, '│'));
675
+ console.log(' ' + c(C.dim, '│ Response:'));
676
+ console.log(' ' + c(C.dim, '│'));
677
+ var lines = (result.content || '').split('\n');
678
+ for (var li = 0; li < lines.length; li++) {
679
+ console.log(' ' + c(C.dim, '│ ') + lines[li]);
680
+ }
681
+ console.log(' ' + c(C.dim, '│'));
682
+ console.log(' ' + c(C.dim, '└──────────────────────────────────────────────────────'));
683
+ console.log('');
684
+ }
685
+
686
+ function printEnsembleResults(ensembleResult) {
687
+ console.log('');
688
+ console.log(' ' + c(C.dim, '┌─') + c(C.bold, ' A3M Ensemble (') + c(C.cyan, String(ensembleResult.totalResults)) + c(C.bold, ' responses, ') + c(C.yellow, String(ensembleResult.totalFailures)) + c(C.bold, ' failed) ') + c(C.dim, '────'));
689
+ console.log('');
690
+
691
+ var results = ensembleResult.results || [];
692
+
693
+ for (var ri = 0; ri < results.length; ri++) {
694
+ var r = results[ri];
695
+ var pColor = colorize(r.provider, r.provider);
696
+
697
+ console.log(' ' + c(C.dim, '── ') + c(C.bold, '[' + String(ri + 1) + '] ') + pColor + c(C.dim, ' (') + r.model + ', ' + r.latency + 'ms, ' + (r.cost > 0 ? '$' + r.cost.toFixed(6) : 'free') + ')');
698
+ console.log('');
699
+
700
+ var lines2 = (r.content || '').substring(0, 1000).split('\n');
701
+ for (var lj = 0; lj < Math.min(lines2.length, 20); lj++) {
702
+ console.log(' ' + lines2[lj]);
703
+ }
704
+ if (r.content.length > 1000) {
705
+ console.log(c(C.dim, ' ... (' + (r.content.length - 1000) + ' more chars)'));
706
+ }
707
+ console.log('');
708
+ }
709
+
710
+ if (ensembleResult.failures && ensembleResult.failures.length > 0) {
711
+ console.log(' ' + c(C.yellow, ' Failed:'));
712
+ for (var fi = 0; fi < ensembleResult.failures.length; fi++) {
713
+ console.log(' ' + c(C.red, '✗ ') + c(C.bold, ensembleResult.failures[fi].provider) + c(C.dim, ': ' + ensembleResult.failures[fi].error.substring(0, 120)));
714
+ }
715
+ console.log('');
716
+ }
717
+
718
+ console.log(' ' + c(C.dim, '└──────────────────────────────────────────────────────'));
719
+ console.log('');
720
+ }
721
+
722
+ // =====================================================================
723
+ // MAIN CLI
724
+ // =====================================================================
725
+
726
+ async function main() {
727
+ var args = process.argv.slice(2);
728
+
729
+ // Extract flags
730
+ var flags = { provider: null, ensemble: false, fallback: false, listProviders: false, help: false, verbose: false, maxEnsemble: 5 };
731
+
732
+ var queryParts = [];
733
+
734
+ for (var ai = 0; ai < args.length; ai++) {
735
+ var arg = args[ai];
736
+ if (arg === '--help' || arg === '-h') {
737
+ flags.help = true;
738
+ } else if (arg === '--list-providers') {
739
+ flags.listProviders = true;
740
+ } else if (arg === '--ensemble' || arg === '-e') {
741
+ flags.ensemble = true;
742
+ } else if (arg === '--fallback' || arg === '-f') {
743
+ flags.fallback = true;
744
+ } else if (arg === '--verbose' || arg === '-v') {
745
+ flags.verbose = true;
746
+ } else if (arg === '--provider' || arg === '-p') {
747
+ flags.provider = args[++ai];
748
+ } else if (arg === '--max-ensemble' || arg === '-m') {
749
+ flags.maxEnsemble = parseInt(args[++ai], 10) || 5;
750
+ } else if (arg.startsWith('--')) {
751
+ // unknown flag, skip
752
+ } else {
753
+ queryParts.push(arg);
754
+ }
755
+ }
756
+
757
+ if (flags.help) {
758
+ printHelp();
759
+ return;
760
+ }
761
+
762
+ if (flags.listProviders) {
763
+ printProviders();
764
+ return;
765
+ }
766
+
767
+ var query = queryParts.join(' ');
768
+
769
+ // If no query, show interactive mode
770
+ if (!query) {
771
+ // Check if we should read from stdin
772
+ var stdin = '';
773
+ if (!process.stdin.isTTY) {
774
+ // Read from pipe
775
+ var chunks = [];
776
+ for await (var chunk of process.stdin) chunks.push(chunk);
777
+ stdin = Buffer.concat(chunks).toString('utf-8').trim();
778
+ }
779
+
780
+ if (stdin) {
781
+ query = stdin;
782
+ } else {
783
+ printHelp();
784
+ return;
785
+ }
786
+ }
787
+
788
+ // Print banner
789
+ printBanner();
790
+
791
+ // Check if any providers available
792
+ var available = getAvailableProviders();
793
+ if (available.length === 0) {
794
+ console.log(c(C.red, ' ERROR: No providers available.'));
795
+ console.log(' Set at least one API key as an environment variable:');
796
+ console.log(' export GROQ_API_KEY=gsk_...');
797
+ console.log(' export NVIDIA_API_KEY=nvapi-...');
798
+ console.log(' export GOOGLE_API_KEY=...');
799
+ console.log('');
800
+ console.log(' Or run with --list-providers to see all options.\n');
801
+ process.exit(1);
802
+ }
803
+
804
+ console.log(c(C.dim, ' Query: ') + c(C.italic, '"' + query.substring(0, 80) + (query.length > 80 ? '...' : '') + '"'));
805
+ console.log(c(C.dim, ' Providers available: ') + c(C.green, String(available.length)));
806
+ console.log('');
807
+
808
+ // Route based on mode
809
+ if (flags.ensemble) {
810
+ var ensembleResult = await runEnsemble(query, { maxProviders: flags.maxEnsemble });
811
+ printEnsembleResults(ensembleResult);
812
+ } else if (flags.fallback || flags.provider) {
813
+ var result = await withFallback(query, { providerFilter: flags.provider });
814
+ if (result.success) {
815
+ printResult(result, flags.verbose);
816
+ } else {
817
+ printResult(result, flags.verbose);
818
+ }
819
+ } else {
820
+ // Default: use highest priority available provider
821
+ var defaultResult = await withFallback(query, { providerFilter: available[0].id });
822
+ if (defaultResult.success) {
823
+ printResult(defaultResult, flags.verbose);
824
+ // Show tip about ensemble
825
+ console.log(c(C.dim, ' 💡 Tip: Try ') + c(C.cyan, '--ensemble') + c(C.dim, ' to compare responses from all providers.'));
826
+ console.log(c(C.dim, ' 💡 Tip: Try ') + c(C.green, '--provider mistral') + c(C.dim, ' to use a specific provider.'));
827
+ console.log('');
828
+ } else {
829
+ // Fallback: try without filter (full fallback chain)
830
+ console.log(c(C.yellow, ' Primary provider failed. Trying full fallback chain...\n'));
831
+ var fallbackResult = await withFallback(query);
832
+ printResult(fallbackResult, flags.verbose);
833
+ }
834
+ }
835
+ }
836
+
837
+ main().catch(function (err) {
838
+ console.error(c(C.red, '\n UNEXPECTED ERROR: ') + err.message + '\n');
839
+ process.exit(1);
840
+ });