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