adaptive-memory-multi-model-router 1.9.5 → 2.0.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.
Files changed (121) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +50 -0
  2. package/.github/ISSUE_TEMPLATE/config.yml +11 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +37 -0
  4. package/.github/PULL_REQUEST_TEMPLATE.md +44 -0
  5. package/.github/workflows/npm-stats-validation.yml +152 -0
  6. package/.github/workflows/pages.yml +37 -0
  7. package/CHANGELOG.md +122 -0
  8. package/CODE_OF_CONDUCT.md +128 -0
  9. package/CONTRIBUTING.md +110 -0
  10. package/LAUNCH-PAIN-DRIVEN.md +339 -0
  11. package/LAUNCH.md +575 -0
  12. package/POPULARITY_BOOSTERS.md +285 -0
  13. package/README.md +231 -300
  14. package/SECURITY.md +69 -0
  15. package/articles/CONTENT_STRUCTURE.md +292 -0
  16. package/articles/DEVTO_COST_GUIDE.md +473 -0
  17. package/articles/DEVTO_FINAL.md +416 -0
  18. package/articles/DEVTO_MULTI_PROVIDER.md +542 -0
  19. package/articles/HN_10X_BETTER.md +430 -0
  20. package/articles/HN_CHINESE_STYLE.md +308 -0
  21. package/articles/HN_FINAL.md +199 -0
  22. package/articles/HN_POSTED_VERSION.md +56 -0
  23. package/articles/HN_RESEARCH.md +364 -0
  24. package/articles/PAIN-DRIVEN-devto-v2.md +308 -0
  25. package/articles/PAIN-DRIVEN-devto-v3.md +268 -0
  26. package/articles/PAIN-DRIVEN-devto.md +242 -0
  27. package/articles/PAIN-DRIVEN-hackernews-v2.md +138 -0
  28. package/articles/PAIN-DRIVEN-hackernews-v3.md +151 -0
  29. package/articles/PAIN-DRIVEN-hackernews.md +131 -0
  30. package/articles/PAIN-DRIVEN-reddit-v2.md +301 -0
  31. package/articles/PAIN-DRIVEN-reddit-v3.md +236 -0
  32. package/articles/PAIN-DRIVEN-reddit.md +218 -0
  33. package/articles/PAIN-DRIVEN-twitter-v2.md +110 -0
  34. package/articles/PAIN-DRIVEN-twitter-v3.md +121 -0
  35. package/articles/PAIN-DRIVEN-twitter.md +120 -0
  36. package/articles/PORTKEY_VS_A3M.md +147 -0
  37. package/articles/REDDIT_FINAL.md +232 -0
  38. package/articles/TWITTER_FINAL.md +167 -0
  39. package/articles/WHY_10X_BETTER.md +261 -0
  40. package/articles/WHY_CHINESE_STYLE_BETTER.md +323 -0
  41. package/articles/hashnode-llm-cost-optimization.md +125 -0
  42. package/articles/medium-building-llm-router.md +205 -0
  43. package/articles/twitter-thread-cost-savings.md +98 -0
  44. package/articles/youtube-tutorial-script.md +262 -0
  45. package/assets/banner.svg +109 -0
  46. package/assets/logo.svg +68 -0
  47. package/assets/social-preview.svg +64 -0
  48. package/demo/demo-script.md +53 -0
  49. package/dist/analytics/costAnalytics.d.ts +77 -0
  50. package/dist/analytics/costAnalytics.d.ts.map +1 -0
  51. package/dist/analytics/costAnalytics.js +219 -0
  52. package/dist/analytics/costAnalytics.js.map +1 -0
  53. package/dist/cache/semanticCache.d.ts +62 -0
  54. package/dist/cache/semanticCache.d.ts.map +1 -0
  55. package/dist/cache/semanticCache.js +176 -0
  56. package/dist/cache/semanticCache.js.map +1 -0
  57. package/dist/cli.js +35 -0
  58. package/dist/index.d.ts +4 -723
  59. package/dist/index.js +11 -362
  60. package/dist/index.js.map +1 -1
  61. package/dist/integrations/langchainAdapter.d.ts +146 -0
  62. package/dist/integrations/langchainAdapter.d.ts.map +1 -0
  63. package/dist/integrations/langchainAdapter.js +731 -0
  64. package/dist/integrations/langchainAdapter.js.map +1 -0
  65. package/dist/integrations/oauth.d.ts +69 -0
  66. package/dist/integrations/oauth.d.ts.map +1 -0
  67. package/dist/integrations/oauth.js +225 -21
  68. package/dist/integrations/oauth.js.map +1 -0
  69. package/dist/memory/autoFetch.d.ts +39 -0
  70. package/dist/memory/autoFetch.d.ts.map +1 -0
  71. package/dist/memory/autoFetch.js +80 -88
  72. package/dist/memory/autoFetch.js.map +1 -0
  73. package/dist/memory/memoryTree.d.ts +76 -0
  74. package/dist/memory/memoryTree.d.ts.map +1 -0
  75. package/dist/memory/memoryTree.js +185 -130
  76. package/dist/memory/memoryTree.js.map +1 -0
  77. package/dist/memory/obsidianVault.d.ts +71 -0
  78. package/dist/memory/obsidianVault.d.ts.map +1 -0
  79. package/dist/memory/obsidianVault.js +207 -22
  80. package/dist/memory/obsidianVault.js.map +1 -0
  81. package/dist/providers/providerConfig.d.ts +49 -0
  82. package/dist/providers/providerConfig.d.ts.map +1 -0
  83. package/dist/providers/providerConfig.js +806 -401
  84. package/dist/providers/providerConfig.js.map +1 -0
  85. package/dist/security/guardrails.d.ts +76 -0
  86. package/dist/security/guardrails.d.ts.map +1 -0
  87. package/dist/security/guardrails.js +479 -0
  88. package/dist/security/guardrails.js.map +1 -0
  89. package/dist/server/dashboard.d.ts +58 -0
  90. package/dist/server/dashboard.d.ts.map +1 -0
  91. package/dist/server/dashboard.js +553 -0
  92. package/dist/server/dashboard.js.map +1 -0
  93. package/dist/server/modelMapper.d.ts +43 -0
  94. package/dist/server/modelMapper.d.ts.map +1 -0
  95. package/dist/server/modelMapper.js +154 -0
  96. package/dist/server/modelMapper.js.map +1 -0
  97. package/dist/server/proxyServer.d.ts +41 -0
  98. package/dist/server/proxyServer.d.ts.map +1 -0
  99. package/dist/server/proxyServer.js +932 -0
  100. package/dist/server/proxyServer.js.map +1 -0
  101. package/dist/skills/__tests__/skill_manager.test.d.ts +2 -0
  102. package/dist/skills/__tests__/skill_manager.test.d.ts.map +1 -0
  103. package/dist/skills/__tests__/skill_manager.test.js +268 -0
  104. package/dist/skills/__tests__/skill_manager.test.js.map +1 -0
  105. package/docs-site/index.html +347 -0
  106. package/package.json +53 -7
  107. package/playground/README.md +51 -0
  108. package/playground/codesandbox.json +12 -0
  109. package/playground/index.js +39 -0
  110. package/scripts/update-npm-badges.js +158 -0
  111. package/src/analytics/costAnalytics.ts +304 -0
  112. package/src/cache/semanticCache.ts +221 -0
  113. package/src/index.ts +6 -0
  114. package/src/integrations/langchainAdapter.ts +955 -0
  115. package/src/providers/providerConfig.ts +923 -0
  116. package/src/security/guardrails.ts +585 -0
  117. package/src/server/dashboard.ts +610 -0
  118. package/src/server/modelMapper.ts +182 -0
  119. package/src/server/proxyServer.ts +1105 -0
  120. package/src/types/langchain.d.ts +83 -0
  121. package/tsconfig.build.json +20 -0
@@ -0,0 +1,923 @@
1
+ /**
2
+ * A3M Router - Generic Provider Configuration System
3
+ *
4
+ * Users can configure their available LLM providers via:
5
+ * 1. Environment variables (*_API_KEY patterns)
6
+ * 2. Config file at ~/.config/a3m-router/providers.json
7
+ * 3. Runtime registration via registerProvider()
8
+ *
9
+ * 40+ providers across free, cheap, mid-tier, premium, and enterprise tiers.
10
+ */
11
+
12
+ import * as fs from 'fs';
13
+ import * as path from 'path';
14
+
15
+ // ============================================================
16
+ // TYPES
17
+ // ============================================================
18
+
19
+ export type ProviderTier = 'free' | 'cheap' | 'mid' | 'premium' | 'enterprise';
20
+ export type ProviderFormat = 'openai' | 'anthropic' | 'google' | 'cohere' | 'aws-bedrock' | 'google-vertex';
21
+ export type ProviderType = 'api' | 'cli' | 'local';
22
+
23
+ export interface ProviderCost {
24
+ input: number; // per 1M tokens
25
+ output: number; // per 1M tokens
26
+ }
27
+
28
+ export interface ProviderDefinition {
29
+ id: string;
30
+ name: string;
31
+ baseUrl: string;
32
+ apiKeyEnv: string;
33
+ models: string[];
34
+ costPerK: ProviderCost;
35
+ tier: ProviderTier;
36
+ format: ProviderFormat;
37
+ type: ProviderType;
38
+ priority: number;
39
+ maxTokens: number;
40
+ cliCommand?: string;
41
+ apiKey?: string | null;
42
+ }
43
+
44
+ // ============================================================
45
+ // DEFAULT PROVIDER DEFINITIONS
46
+ // ============================================================
47
+
48
+ export const DEFAULT_PROVIDERS: Record<string, ProviderDefinition> = {
49
+ // ========================================================================
50
+ // TIER: FREE / LOCAL
51
+ // ========================================================================
52
+ ollama: {
53
+ id: 'ollama',
54
+ name: 'Ollama',
55
+ baseUrl: 'http://127.0.0.1:11434/v1/chat/completions',
56
+ apiKeyEnv: '',
57
+ models: ['llama3', 'mistral', 'qwen2', 'codellama', 'phi3', 'gemma2'],
58
+ costPerK: { input: 0, output: 0 },
59
+ tier: 'free',
60
+ format: 'openai',
61
+ type: 'local',
62
+ priority: 1,
63
+ maxTokens: 8192,
64
+ },
65
+
66
+ lmstudio: {
67
+ id: 'lmstudio',
68
+ name: 'LM Studio',
69
+ baseUrl: 'http://127.0.0.1:1234/v1/chat/completions',
70
+ apiKeyEnv: '',
71
+ models: [],
72
+ costPerK: { input: 0, output: 0 },
73
+ tier: 'free',
74
+ format: 'openai',
75
+ type: 'local',
76
+ priority: 2,
77
+ maxTokens: 8192,
78
+ },
79
+
80
+ vllm: {
81
+ id: 'vllm',
82
+ name: 'vLLM',
83
+ baseUrl: 'http://127.0.0.1:8000/v1/chat/completions',
84
+ apiKeyEnv: '',
85
+ models: [],
86
+ costPerK: { input: 0, output: 0 },
87
+ tier: 'free',
88
+ format: 'openai',
89
+ type: 'local',
90
+ priority: 3,
91
+ maxTokens: 8192,
92
+ },
93
+
94
+ google: {
95
+ id: 'google',
96
+ name: 'Google AI',
97
+ baseUrl: 'https://generativelanguage.googleapis.com/v1beta/models',
98
+ apiKeyEnv: 'GOOGLE_API_KEY',
99
+ models: [
100
+ 'gemini-2.5-flash',
101
+ 'gemini-2.5-pro',
102
+ 'gemini-2.0-flash',
103
+ 'gemini-1.5-flash',
104
+ 'gemini-1.5-pro',
105
+ 'gemma-3-27b-it',
106
+ ],
107
+ costPerK: { input: 0, output: 0 }, // Free tier available
108
+ tier: 'free',
109
+ format: 'google',
110
+ type: 'api',
111
+ priority: 4,
112
+ maxTokens: 8192,
113
+ },
114
+
115
+ // ========================================================================
116
+ // TIER: CHEAP / FAST (inference-optimized)
117
+ // ========================================================================
118
+ groq: {
119
+ id: 'groq',
120
+ name: 'Groq',
121
+ baseUrl: 'https://api.groq.com/openai/v1/chat/completions',
122
+ apiKeyEnv: 'GROQ_API_KEY',
123
+ models: [
124
+ 'llama-3.3-70b-versatile',
125
+ 'llama-3.1-8b-instant',
126
+ 'openai/gpt-oss-120b',
127
+ 'openai/gpt-oss-20b',
128
+ 'qwen/qwen3-32b',
129
+ 'meta-llama/llama-4-scout-17b-16e-instruct',
130
+ ],
131
+ costPerK: { input: 0.59, output: 0.79 },
132
+ tier: 'cheap',
133
+ format: 'openai',
134
+ type: 'api',
135
+ priority: 5,
136
+ maxTokens: 8192,
137
+ },
138
+
139
+ cerebras: {
140
+ id: 'cerebras',
141
+ name: 'Cerebras',
142
+ baseUrl: 'https://api.cerebras.ai/v1/chat/completions',
143
+ apiKeyEnv: 'CEREBRAS_API_KEY',
144
+ models: [
145
+ 'llama3.1-8b',
146
+ 'llama-3.3-70b',
147
+ 'qwen-3-235b-a22b-instruct-2507',
148
+ 'gpt-oss-120b',
149
+ 'zai-glm-4.7',
150
+ ],
151
+ costPerK: { input: 0.6, output: 0.6 },
152
+ tier: 'cheap',
153
+ format: 'openai',
154
+ type: 'api',
155
+ priority: 6,
156
+ maxTokens: 8192,
157
+ },
158
+
159
+ deepinfra: {
160
+ id: 'deepinfra',
161
+ name: 'DeepInfra',
162
+ baseUrl: 'https://api.deepinfra.com/v1/openai/chat/completions',
163
+ apiKeyEnv: 'DEEPINFRA_API_KEY',
164
+ models: [
165
+ 'meta-llama/Meta-Llama-3.1-8B-Instruct',
166
+ 'meta-llama/Meta-Llama-3.1-70B-Instruct',
167
+ 'mistralai/Mixtral-8x7B-Instruct-v0.1',
168
+ 'Qwen/Qwen2.5-72B-Instruct',
169
+ 'google/gemma-2-27b-it',
170
+ ],
171
+ costPerK: { input: 0.05, output: 0.05 },
172
+ tier: 'cheap',
173
+ format: 'openai',
174
+ type: 'api',
175
+ priority: 7,
176
+ maxTokens: 8192,
177
+ },
178
+
179
+ together: {
180
+ id: 'together',
181
+ name: 'Together AI',
182
+ baseUrl: 'https://api.together.xyz/v1/chat/completions',
183
+ apiKeyEnv: 'TOGETHER_API_KEY',
184
+ models: [
185
+ 'meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo',
186
+ 'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo',
187
+ 'mistralai/Mixtral-8x7B-Instruct-v0.1',
188
+ 'Qwen/Qwen2.5-72B-Instruct-Turbo',
189
+ 'google/gemma-2-27b-it',
190
+ ],
191
+ costPerK: { input: 0.18, output: 0.18 },
192
+ tier: 'cheap',
193
+ format: 'openai',
194
+ type: 'api',
195
+ priority: 8,
196
+ maxTokens: 8192,
197
+ },
198
+
199
+ fireworks: {
200
+ id: 'fireworks',
201
+ name: 'Fireworks AI',
202
+ baseUrl: 'https://api.fireworks.ai/inference/v1/chat/completions',
203
+ apiKeyEnv: 'FIREWORKS_API_KEY',
204
+ models: [
205
+ 'accounts/fireworks/models/llama-v3p1-8b-instruct',
206
+ 'accounts/fireworks/models/llama-v3p1-70b-instruct',
207
+ 'accounts/fireworks/models/mixtral-8x7b-instruct',
208
+ 'accounts/fireworks/models/qwen2p5-72b-instruct',
209
+ ],
210
+ costPerK: { input: 0.2, output: 0.2 },
211
+ tier: 'cheap',
212
+ format: 'openai',
213
+ type: 'api',
214
+ priority: 9,
215
+ maxTokens: 8192,
216
+ },
217
+
218
+ novita: {
219
+ id: 'novita',
220
+ name: 'Novita AI',
221
+ baseUrl: 'https://api.novita.ai/v3/openai/chat/completions',
222
+ apiKeyEnv: 'NOVITA_API_KEY',
223
+ models: [
224
+ 'meta-llama/llama-3.1-8b-instruct',
225
+ 'meta-llama/llama-3.1-70b-instruct',
226
+ 'Qwen/Qwen2.5-72B-Instruct',
227
+ ],
228
+ costPerK: { input: 0.06, output: 0.06 },
229
+ tier: 'cheap',
230
+ format: 'openai',
231
+ type: 'api',
232
+ priority: 10,
233
+ maxTokens: 8192,
234
+ },
235
+
236
+ sambanova: {
237
+ id: 'sambanova',
238
+ name: 'SambaNova',
239
+ baseUrl: 'https://api.sambanova.ai/v1/chat/completions',
240
+ apiKeyEnv: 'SAMBANOVA_API_KEY',
241
+ models: [
242
+ 'Meta-Llama-3.1-8B-Instruct',
243
+ 'Meta-Llama-3.1-70B-Instruct',
244
+ ],
245
+ costPerK: { input: 0.1, output: 0.1 },
246
+ tier: 'cheap',
247
+ format: 'openai',
248
+ type: 'api',
249
+ priority: 11,
250
+ maxTokens: 8192,
251
+ },
252
+
253
+ anyscale: {
254
+ id: 'anyscale',
255
+ name: 'Anyscale',
256
+ baseUrl: 'https://api.endpoints.anyscale.com/v1/chat/completions',
257
+ apiKeyEnv: 'ANYSCALE_API_KEY',
258
+ models: [
259
+ 'meta-llama/Meta-Llama-3.1-8B-Instruct',
260
+ 'meta-llama/Meta-Llama-3.1-70B-Instruct',
261
+ 'mistralai/Mixtral-8x7B-Instruct-v0.1',
262
+ ],
263
+ costPerK: { input: 0.15, output: 0.15 },
264
+ tier: 'cheap',
265
+ format: 'openai',
266
+ type: 'api',
267
+ priority: 12,
268
+ maxTokens: 8192,
269
+ },
270
+
271
+ replicate: {
272
+ id: 'replicate',
273
+ name: 'Replicate',
274
+ baseUrl: 'https://api.replicate.com/v1/chat/completions',
275
+ apiKeyEnv: 'REPLICATE_API_KEY',
276
+ models: [
277
+ 'meta/llama-2-70b-chat',
278
+ 'mistralai/mixtral-8x7b-instruct-v0.1',
279
+ ],
280
+ costPerK: { input: 0.2, output: 0.2 },
281
+ tier: 'cheap',
282
+ format: 'openai',
283
+ type: 'api',
284
+ priority: 13,
285
+ maxTokens: 8192,
286
+ },
287
+
288
+ // ========================================================================
289
+ // TIER: MID (good quality/price ratio)
290
+ // ========================================================================
291
+ deepseek: {
292
+ id: 'deepseek',
293
+ name: 'DeepSeek',
294
+ baseUrl: 'https://api.deepseek.com/v1/chat/completions',
295
+ apiKeyEnv: 'DEEPSEEK_API_KEY',
296
+ models: ['deepseek-chat', 'deepseek-reasoner', 'deepseek-coder'],
297
+ costPerK: { input: 0.14, output: 0.28 },
298
+ tier: 'mid',
299
+ format: 'openai',
300
+ type: 'api',
301
+ priority: 14,
302
+ maxTokens: 8192,
303
+ },
304
+
305
+ mistral: {
306
+ id: 'mistral',
307
+ name: 'Mistral',
308
+ baseUrl: 'https://api.mistral.ai/v1/chat/completions',
309
+ apiKeyEnv: 'MISTRAL_API_KEY',
310
+ models: [
311
+ 'mistral-small-latest',
312
+ 'mistral-medium-latest',
313
+ 'mistral-large-latest',
314
+ 'mistral-small-2506',
315
+ 'devstral-small-2507',
316
+ 'ministral-3b-latest',
317
+ 'ministral-8b-latest',
318
+ 'codestral-latest',
319
+ 'open-mistral-nemo',
320
+ ],
321
+ costPerK: { input: 0.2, output: 0.6 },
322
+ tier: 'mid',
323
+ format: 'openai',
324
+ type: 'api',
325
+ priority: 15,
326
+ maxTokens: 8192,
327
+ },
328
+
329
+ perplexity: {
330
+ id: 'perplexity',
331
+ name: 'Perplexity',
332
+ baseUrl: 'https://api.perplexity.ai/chat/completions',
333
+ apiKeyEnv: 'PERPLEXITY_API_KEY',
334
+ models: [
335
+ 'llama-3.1-sonar-small-128k-online',
336
+ 'llama-3.1-sonar-large-128k-online',
337
+ 'sonar-pro',
338
+ ],
339
+ costPerK: { input: 1.0, output: 1.0 },
340
+ tier: 'mid',
341
+ format: 'openai',
342
+ type: 'api',
343
+ priority: 16,
344
+ maxTokens: 8192,
345
+ },
346
+
347
+ cohere: {
348
+ id: 'cohere',
349
+ name: 'Cohere',
350
+ baseUrl: 'https://api.cohere.ai/v1/chat/completions',
351
+ apiKeyEnv: 'COHERE_API_KEY',
352
+ models: ['command-r-plus', 'command-r', 'command-a-03-2025'],
353
+ costPerK: { input: 2.5, output: 10 },
354
+ tier: 'mid',
355
+ format: 'cohere',
356
+ type: 'api',
357
+ priority: 17,
358
+ maxTokens: 8192,
359
+ },
360
+
361
+ ai21: {
362
+ id: 'ai21',
363
+ name: 'AI21 Labs',
364
+ baseUrl: 'https://api.ai21.com/studio/v1/chat/completions',
365
+ apiKeyEnv: 'AI21_API_KEY',
366
+ models: ['jamba-1.5-mini', 'jamba-1.5-large'],
367
+ costPerK: { input: 0.2, output: 0.4 },
368
+ tier: 'mid',
369
+ format: 'openai',
370
+ type: 'api',
371
+ priority: 18,
372
+ maxTokens: 8192,
373
+ },
374
+
375
+ // ========================================================================
376
+ // TIER: PREMIUM (high-quality frontier models)
377
+ // ========================================================================
378
+ openai: {
379
+ id: 'openai',
380
+ name: 'OpenAI',
381
+ baseUrl: 'https://api.openai.com/v1/chat/completions',
382
+ apiKeyEnv: 'OPENAI_API_KEY',
383
+ models: [
384
+ 'gpt-4o',
385
+ 'gpt-4o-mini',
386
+ 'gpt-4-turbo',
387
+ 'gpt-4',
388
+ 'gpt-3.5-turbo',
389
+ 'o1-preview',
390
+ 'o1-mini',
391
+ ],
392
+ costPerK: { input: 2.5, output: 10 },
393
+ tier: 'premium',
394
+ format: 'openai',
395
+ type: 'api',
396
+ priority: 19,
397
+ maxTokens: 8192,
398
+ },
399
+
400
+ anthropic: {
401
+ id: 'anthropic',
402
+ name: 'Anthropic',
403
+ baseUrl: 'https://api.anthropic.com/v1/messages',
404
+ apiKeyEnv: 'ANTHROPIC_API_KEY',
405
+ models: [
406
+ 'claude-sonnet-4-20250514',
407
+ 'claude-3.5-sonnet',
408
+ 'claude-3-opus',
409
+ 'claude-3-haiku',
410
+ ],
411
+ costPerK: { input: 3, output: 15 },
412
+ tier: 'premium',
413
+ format: 'anthropic',
414
+ type: 'api',
415
+ priority: 20,
416
+ maxTokens: 8192,
417
+ },
418
+
419
+ xai: {
420
+ id: 'xai',
421
+ name: 'xAI',
422
+ baseUrl: 'https://api.x.ai/v1/chat/completions',
423
+ apiKeyEnv: 'XAI_API_KEY',
424
+ models: ['grok-3', 'grok-3-mini', 'grok-2', 'grok-2-mini'],
425
+ costPerK: { input: 3.0, output: 15.0 },
426
+ tier: 'premium',
427
+ format: 'openai',
428
+ type: 'api',
429
+ priority: 21,
430
+ maxTokens: 8192,
431
+ },
432
+
433
+ // ========================================================================
434
+ // TIER: ENTERPRISE (cloud-managed models)
435
+ // ========================================================================
436
+ azure_openai: {
437
+ id: 'azure_openai',
438
+ name: 'Azure OpenAI',
439
+ baseUrl: 'https://{resource}.openai.azure.com/openai/deployments/{deployment}/chat/completions',
440
+ apiKeyEnv: 'AZURE_OPENAI_API_KEY',
441
+ models: ['gpt-4', 'gpt-35-turbo', 'gpt-4o'],
442
+ costPerK: { input: 3.0, output: 12.0 },
443
+ tier: 'enterprise',
444
+ format: 'openai',
445
+ type: 'api',
446
+ priority: 22,
447
+ maxTokens: 8192,
448
+ },
449
+
450
+ bedrock: {
451
+ id: 'bedrock',
452
+ name: 'AWS Bedrock',
453
+ baseUrl: '',
454
+ apiKeyEnv: 'AWS_ACCESS_KEY_ID',
455
+ models: [
456
+ 'anthropic.claude-3-sonnet',
457
+ 'anthropic.claude-3-haiku',
458
+ 'meta.llama3-1-8b',
459
+ 'meta.llama3-1-70b',
460
+ 'mistral.mixtral-8x7b',
461
+ ],
462
+ costPerK: { input: 3.0, output: 15.0 },
463
+ tier: 'enterprise',
464
+ format: 'aws-bedrock',
465
+ type: 'api',
466
+ priority: 23,
467
+ maxTokens: 8192,
468
+ },
469
+
470
+ vertex: {
471
+ id: 'vertex',
472
+ name: 'Google Vertex AI',
473
+ baseUrl: '',
474
+ apiKeyEnv: 'GOOGLE_APPLICATION_CREDENTIALS',
475
+ models: [
476
+ 'gemini-1.5-flash',
477
+ 'gemini-1.5-pro',
478
+ 'claude-3-sonnet',
479
+ 'claude-3-haiku',
480
+ ],
481
+ costPerK: { input: 1.25, output: 5.0 },
482
+ tier: 'enterprise',
483
+ format: 'google-vertex',
484
+ type: 'api',
485
+ priority: 24,
486
+ maxTokens: 8192,
487
+ },
488
+
489
+ // ========================================================================
490
+ // ASIAN PROVIDERS
491
+ // ========================================================================
492
+ zhipu: {
493
+ id: 'zhipu',
494
+ name: 'Zhipu AI (GLM)',
495
+ baseUrl: 'https://open.bigmodel.cn/api/paas/v4/chat/completions',
496
+ apiKeyEnv: 'ZHIPU_API_KEY',
497
+ models: ['glm-4-flash', 'glm-4-plus', 'glm-4-air', 'glm-4-long'],
498
+ costPerK: { input: 0.1, output: 0.1 },
499
+ tier: 'cheap',
500
+ format: 'openai',
501
+ type: 'api',
502
+ priority: 25,
503
+ maxTokens: 8192,
504
+ },
505
+
506
+ moonshot: {
507
+ id: 'moonshot',
508
+ name: 'Moonshot (Kimi)',
509
+ baseUrl: 'https://api.moonshot.cn/v1/chat/completions',
510
+ apiKeyEnv: 'MOONSHOT_API_KEY',
511
+ models: ['moonshot-v1-8k', 'moonshot-v1-32k', 'moonshot-v1-128k'],
512
+ costPerK: { input: 0.14, output: 0.14 },
513
+ tier: 'cheap',
514
+ format: 'openai',
515
+ type: 'api',
516
+ priority: 26,
517
+ maxTokens: 8192,
518
+ },
519
+
520
+ qwen: {
521
+ id: 'qwen',
522
+ name: 'Alibaba Qwen (DashScope)',
523
+ baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
524
+ apiKeyEnv: 'DASHSCOPE_API_KEY',
525
+ models: ['qwen-turbo', 'qwen-plus', 'qwen-max', 'qwen-long'],
526
+ costPerK: { input: 0.4, output: 1.2 },
527
+ tier: 'mid',
528
+ format: 'openai',
529
+ type: 'api',
530
+ priority: 27,
531
+ maxTokens: 8192,
532
+ },
533
+
534
+ yi: {
535
+ id: 'yi',
536
+ name: 'Yi (01.AI)',
537
+ baseUrl: 'https://api.lingyiwanwu.com/v1/chat/completions',
538
+ apiKeyEnv: 'YI_API_KEY',
539
+ models: ['yi-lightning', 'yi-large', 'yi-medium', 'yi-spark'],
540
+ costPerK: { input: 0.2, output: 0.2 },
541
+ tier: 'cheap',
542
+ format: 'openai',
543
+ type: 'api',
544
+ priority: 28,
545
+ maxTokens: 8192,
546
+ },
547
+
548
+ baichuan: {
549
+ id: 'baichuan',
550
+ name: 'Baichuan',
551
+ baseUrl: 'https://api.baichuan-ai.com/v1/chat/completions',
552
+ apiKeyEnv: 'BAICHUAN_API_KEY',
553
+ models: ['Baichuan4', 'Baichuan3-Turbo', 'Baichuan3-Turbo-128k'],
554
+ costPerK: { input: 0.2, output: 0.2 },
555
+ tier: 'cheap',
556
+ format: 'openai',
557
+ type: 'api',
558
+ priority: 29,
559
+ maxTokens: 8192,
560
+ },
561
+
562
+ minimax: {
563
+ id: 'minimax',
564
+ name: 'MiniMax',
565
+ baseUrl: 'https://api.minimax.chat/v1/text/chatcompletion_v2',
566
+ apiKeyEnv: 'MINIMAX_API_KEY',
567
+ models: ['MiniMax-Text-01', 'abab6.5s-chat'],
568
+ costPerK: { input: 0.1, output: 0.1 },
569
+ tier: 'cheap',
570
+ format: 'openai',
571
+ type: 'api',
572
+ priority: 30,
573
+ maxTokens: 8192,
574
+ },
575
+
576
+ stepfun: {
577
+ id: 'stepfun',
578
+ name: 'StepFun',
579
+ baseUrl: 'https://api.stepfun.com/v1/chat/completions',
580
+ apiKeyEnv: 'STEPFUN_API_KEY',
581
+ models: ['step-1-8k', 'step-1-32k', 'step-2-16k'],
582
+ costPerK: { input: 0.2, output: 0.2 },
583
+ tier: 'mid',
584
+ format: 'openai',
585
+ type: 'api',
586
+ priority: 31,
587
+ maxTokens: 8192,
588
+ },
589
+
590
+ // ========================================================================
591
+ // EUROPEAN PROVIDERS
592
+ // ========================================================================
593
+ alephalpha: {
594
+ id: 'alephalpha',
595
+ name: 'Aleph Alpha',
596
+ baseUrl: 'https://api.aleph-alpha.com/v1/chat/completions',
597
+ apiKeyEnv: 'ALEPH_ALPHA_API_KEY',
598
+ models: ['luminous-base', 'luminous-extended', 'luminous-supreme'],
599
+ costPerK: { input: 2.0, output: 2.0 },
600
+ tier: 'mid',
601
+ format: 'openai',
602
+ type: 'api',
603
+ priority: 32,
604
+ maxTokens: 8192,
605
+ },
606
+
607
+ deepset: {
608
+ id: 'deepset',
609
+ name: 'Deepset',
610
+ baseUrl: 'https://api.deepset.ai/v1/chat/completions',
611
+ apiKeyEnv: 'DEEPSET_API_KEY',
612
+ models: ['gpt-4', 'claude-3-sonnet'],
613
+ costPerK: { input: 3.0, output: 12.0 },
614
+ tier: 'mid',
615
+ format: 'openai',
616
+ type: 'api',
617
+ priority: 33,
618
+ maxTokens: 8192,
619
+ },
620
+
621
+ // ========================================================================
622
+ // OPEN ROUTER / AGGREGATORS
623
+ // ========================================================================
624
+ openrouter: {
625
+ id: 'openrouter',
626
+ name: 'OpenRouter',
627
+ baseUrl: 'https://openrouter.ai/api/v1/chat/completions',
628
+ apiKeyEnv: 'OPENROUTER_API_KEY',
629
+ models: [
630
+ 'openai/gpt-4o',
631
+ 'anthropic/claude-3.5-sonnet',
632
+ 'google/gemini-pro-1.5',
633
+ 'meta-llama/llama-3.1-70b-instruct',
634
+ 'mistralai/mistral-large',
635
+ ],
636
+ costPerK: { input: 0, output: 0 }, // Passthrough pricing
637
+ tier: 'cheap',
638
+ format: 'openai',
639
+ type: 'api',
640
+ priority: 34,
641
+ maxTokens: 8192,
642
+ },
643
+
644
+ // ========================================================================
645
+ // CLI PROVIDERS (local tools)
646
+ // ========================================================================
647
+ opencode: {
648
+ id: 'opencode',
649
+ name: 'OpenCode',
650
+ baseUrl: '',
651
+ apiKeyEnv: '',
652
+ cliCommand: 'opencode',
653
+ models: [],
654
+ costPerK: { input: 0, output: 0 },
655
+ tier: 'free',
656
+ format: 'openai',
657
+ type: 'cli',
658
+ priority: 35,
659
+ maxTokens: 8192,
660
+ },
661
+
662
+ commandcode: {
663
+ id: 'commandcode',
664
+ name: 'CommandCode',
665
+ baseUrl: 'https://api.commandcode.ai/v1/chat/completions',
666
+ apiKeyEnv: 'COMMANDCODE_API_KEY',
667
+ models: ['taste-1'],
668
+ costPerK: { input: 0, output: 0 },
669
+ tier: 'free',
670
+ format: 'openai',
671
+ type: 'cli',
672
+ cliCommand: 'commandcode',
673
+ priority: 36,
674
+ maxTokens: 8192,
675
+ },
676
+ };
677
+
678
+ // ============================================================
679
+ // RUNTIME STATE
680
+ // ============================================================
681
+
682
+ let _registeredProviders: Record<string, ProviderDefinition> = { ...DEFAULT_PROVIDERS };
683
+ let _configLoaded = false;
684
+
685
+ // ============================================================
686
+ // CONFIGURATION LOADING
687
+ // ============================================================
688
+
689
+ export function loadConfig(configPath?: string): Record<string, ProviderDefinition> {
690
+ const paths: string[] = [];
691
+
692
+ if (configPath && fs.existsSync(configPath)) {
693
+ paths.push(configPath);
694
+ }
695
+
696
+ const userConfig = path.join(
697
+ process.env.HOME || process.env.USERPROFILE || '.',
698
+ '.config', 'a3m-router', 'providers.json'
699
+ );
700
+ if (fs.existsSync(userConfig)) {
701
+ paths.push(userConfig);
702
+ }
703
+
704
+ const projectConfig = path.join(process.cwd(), 'a3m-providers.json');
705
+ if (fs.existsSync(projectConfig)) {
706
+ paths.push(projectConfig);
707
+ }
708
+
709
+ for (const p of paths) {
710
+ try {
711
+ const raw = fs.readFileSync(p, 'utf-8');
712
+ const config = JSON.parse(raw);
713
+ if (config.providers) {
714
+ for (const [id, provider] of Object.entries(config.providers)) {
715
+ const prov = provider as Partial<ProviderDefinition>;
716
+ if (_registeredProviders[id]) {
717
+ _registeredProviders[id] = { ..._registeredProviders[id], ...prov };
718
+ } else {
719
+ _registeredProviders[id] = {
720
+ id,
721
+ type: 'api',
722
+ priority: 50,
723
+ maxTokens: 8192,
724
+ costPerK: { input: 0, output: 0 },
725
+ tier: 'mid',
726
+ format: 'openai',
727
+ models: [],
728
+ name: id,
729
+ baseUrl: '',
730
+ apiKeyEnv: '',
731
+ ...prov,
732
+ } as ProviderDefinition;
733
+ }
734
+ }
735
+ }
736
+ _configLoaded = true;
737
+ break;
738
+ } catch {
739
+ // Skip invalid config files
740
+ }
741
+ }
742
+
743
+ // Load API keys from environment
744
+ for (const provider of Object.values(_registeredProviders)) {
745
+ if (provider.apiKeyEnv) {
746
+ provider.apiKey = process.env[provider.apiKeyEnv] || null;
747
+ }
748
+ }
749
+
750
+ return _registeredProviders;
751
+ }
752
+
753
+ export function getAvailableProviders(): Record<string, ProviderDefinition> {
754
+ if (!_configLoaded) {
755
+ loadConfig();
756
+ }
757
+
758
+ const available: Record<string, ProviderDefinition> = {};
759
+
760
+ for (const [id, provider] of Object.entries(_registeredProviders)) {
761
+ if (provider.type === 'api') {
762
+ if (provider.apiKey) {
763
+ available[id] = provider;
764
+ }
765
+ } else {
766
+ available[id] = provider;
767
+ }
768
+ }
769
+
770
+ return Object.entries(available)
771
+ .sort(([, a], [, b]) => a.priority - b.priority)
772
+ .reduce((acc: Record<string, ProviderDefinition>, [k, v]) => { acc[k] = v; return acc; }, {});
773
+ }
774
+
775
+ // ============================================================
776
+ // RUNTIME REGISTRATION
777
+ // ============================================================
778
+
779
+ export function registerProvider(id: string, config: Partial<ProviderDefinition>): ProviderDefinition {
780
+ _registeredProviders[id] = {
781
+ id,
782
+ name: config.name || id,
783
+ baseUrl: config.baseUrl || '',
784
+ apiKeyEnv: config.apiKeyEnv || '',
785
+ type: 'api',
786
+ priority: 50,
787
+ maxTokens: 8192,
788
+ costPerK: { input: 0, output: 0 },
789
+ tier: 'mid',
790
+ format: 'openai',
791
+ models: [],
792
+ ...config,
793
+ } as ProviderDefinition;
794
+ return _registeredProviders[id];
795
+ }
796
+
797
+ export function deregisterProvider(id: string): void {
798
+ delete _registeredProviders[id];
799
+ }
800
+
801
+ export function updateProvider(id: string, updates: Partial<ProviderDefinition>): ProviderDefinition | null {
802
+ if (_registeredProviders[id]) {
803
+ _registeredProviders[id] = { ..._registeredProviders[id], ...updates };
804
+ return _registeredProviders[id];
805
+ }
806
+ return null;
807
+ }
808
+
809
+ // ============================================================
810
+ // HEALTH CHECK
811
+ // ============================================================
812
+
813
+ export async function healthCheck(providerId: string): Promise<{
814
+ healthy: boolean;
815
+ error?: string;
816
+ latency?: number;
817
+ model?: string;
818
+ type?: string;
819
+ }> {
820
+ const provider = _registeredProviders[providerId];
821
+ if (!provider) {
822
+ return { healthy: false, error: 'Provider not found: ' + providerId };
823
+ }
824
+
825
+ if (provider.type === 'cli') {
826
+ const { execSync } = require('child_process');
827
+ try {
828
+ execSync(`which ${provider.cliCommand || provider.id}`, { stdio: 'pipe' });
829
+ return { healthy: true, latency: 0, type: 'cli' };
830
+ } catch {
831
+ return { healthy: false, error: 'Command not found: ' + (provider.cliCommand || provider.id) };
832
+ }
833
+ }
834
+
835
+ if (provider.type === 'api') {
836
+ if (!provider.apiKey) {
837
+ return { healthy: false, error: 'No API key for ' + provider.name };
838
+ }
839
+
840
+ const startTime = Date.now();
841
+ try {
842
+ const model = provider.models[0];
843
+ const resp = await fetch(provider.baseUrl, {
844
+ method: 'POST',
845
+ headers: {
846
+ 'Authorization': 'Bearer ' + provider.apiKey,
847
+ 'Content-Type': 'application/json',
848
+ },
849
+ body: JSON.stringify({
850
+ model,
851
+ messages: [{ role: 'user', content: 'test' }],
852
+ max_tokens: 5,
853
+ }),
854
+ });
855
+
856
+ const latency = Date.now() - startTime;
857
+ const data = await resp.json() as any;
858
+
859
+ if (data.error) {
860
+ return { healthy: false, error: data.error.message, latency };
861
+ }
862
+
863
+ return { healthy: true, latency, model: data.model || model };
864
+ } catch (e: any) {
865
+ return { healthy: false, error: e.message, latency: Date.now() - startTime };
866
+ }
867
+ }
868
+
869
+ return { healthy: false, error: 'Unknown provider type: ' + provider.type };
870
+ }
871
+
872
+ export async function checkAllProviders(): Promise<Record<string, any>> {
873
+ const results: Record<string, any> = {};
874
+ const available = getAvailableProviders();
875
+
876
+ for (const id of Object.keys(available)) {
877
+ results[id] = await healthCheck(id);
878
+ }
879
+
880
+ return results;
881
+ }
882
+
883
+ export function findCheapestAvailableProvider(model?: string): ProviderDefinition | null {
884
+ const available = getAvailableProviders();
885
+ const sorted = Object.values(available).sort(
886
+ (a, b) => (a.costPerK.input + a.costPerK.output) - (b.costPerK.input + b.costPerK.output)
887
+ );
888
+ if (model) {
889
+ return sorted.find(p => p.models.includes(model)) || null;
890
+ }
891
+ return sorted[0] || null;
892
+ }
893
+
894
+ export function findFastestAvailableProvider(): ProviderDefinition | null {
895
+ const available = getAvailableProviders();
896
+ const cheapTier = Object.values(available).filter(p => p.tier === 'cheap');
897
+ return cheapTier[0] || Object.values(available)[0] || null;
898
+ }
899
+
900
+ // ============================================================
901
+ // SAVE CONFIG
902
+ // ============================================================
903
+
904
+ export function saveConfig(configPath?: string): string {
905
+ const target = configPath || path.join(
906
+ process.env.HOME || '.',
907
+ '.config', 'a3m-router', 'providers.json'
908
+ );
909
+
910
+ const dir = path.dirname(target);
911
+ if (!fs.existsSync(dir)) {
912
+ fs.mkdirSync(dir, { recursive: true });
913
+ }
914
+
915
+ const safeConfig: Record<string, any> = {};
916
+ for (const [id, provider] of Object.entries(_registeredProviders)) {
917
+ safeConfig[id] = { ...provider };
918
+ delete safeConfig[id].apiKey;
919
+ }
920
+
921
+ fs.writeFileSync(target, JSON.stringify({ providers: safeConfig }, null, 2));
922
+ return target;
923
+ }