tlc-claude-code 1.4.9 → 1.5.2

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 (122) hide show
  1. package/CLAUDE.md +23 -0
  2. package/CODING-STANDARDS.md +408 -0
  3. package/bin/install.js +2 -0
  4. package/dashboard/dist/components/QualityGatePane.d.ts +38 -0
  5. package/dashboard/dist/components/QualityGatePane.js +31 -0
  6. package/dashboard/dist/components/QualityGatePane.test.d.ts +1 -0
  7. package/dashboard/dist/components/QualityGatePane.test.js +147 -0
  8. package/dashboard/dist/components/orchestration/AgentCard.d.ts +26 -0
  9. package/dashboard/dist/components/orchestration/AgentCard.js +60 -0
  10. package/dashboard/dist/components/orchestration/AgentCard.test.d.ts +1 -0
  11. package/dashboard/dist/components/orchestration/AgentCard.test.js +63 -0
  12. package/dashboard/dist/components/orchestration/AgentControls.d.ts +11 -0
  13. package/dashboard/dist/components/orchestration/AgentControls.js +20 -0
  14. package/dashboard/dist/components/orchestration/AgentControls.test.d.ts +1 -0
  15. package/dashboard/dist/components/orchestration/AgentControls.test.js +52 -0
  16. package/dashboard/dist/components/orchestration/AgentDetail.d.ts +35 -0
  17. package/dashboard/dist/components/orchestration/AgentDetail.js +37 -0
  18. package/dashboard/dist/components/orchestration/AgentDetail.test.d.ts +1 -0
  19. package/dashboard/dist/components/orchestration/AgentDetail.test.js +79 -0
  20. package/dashboard/dist/components/orchestration/AgentList.d.ts +31 -0
  21. package/dashboard/dist/components/orchestration/AgentList.js +47 -0
  22. package/dashboard/dist/components/orchestration/AgentList.test.d.ts +1 -0
  23. package/dashboard/dist/components/orchestration/AgentList.test.js +64 -0
  24. package/dashboard/dist/components/orchestration/CostMeter.d.ts +11 -0
  25. package/dashboard/dist/components/orchestration/CostMeter.js +28 -0
  26. package/dashboard/dist/components/orchestration/CostMeter.test.d.ts +1 -0
  27. package/dashboard/dist/components/orchestration/CostMeter.test.js +50 -0
  28. package/dashboard/dist/components/orchestration/ModelSelector.d.ts +20 -0
  29. package/dashboard/dist/components/orchestration/ModelSelector.js +12 -0
  30. package/dashboard/dist/components/orchestration/ModelSelector.test.d.ts +1 -0
  31. package/dashboard/dist/components/orchestration/ModelSelector.test.js +56 -0
  32. package/dashboard/dist/components/orchestration/OrchestrationDashboard.d.ts +28 -0
  33. package/dashboard/dist/components/orchestration/OrchestrationDashboard.js +28 -0
  34. package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.d.ts +1 -0
  35. package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.js +56 -0
  36. package/dashboard/dist/components/orchestration/QualityIndicator.d.ts +11 -0
  37. package/dashboard/dist/components/orchestration/QualityIndicator.js +37 -0
  38. package/dashboard/dist/components/orchestration/QualityIndicator.test.d.ts +1 -0
  39. package/dashboard/dist/components/orchestration/QualityIndicator.test.js +52 -0
  40. package/dashboard/dist/components/orchestration/index.d.ts +8 -0
  41. package/dashboard/dist/components/orchestration/index.js +8 -0
  42. package/package.json +1 -1
  43. package/server/lib/access-control.js +352 -0
  44. package/server/lib/access-control.test.js +322 -0
  45. package/server/lib/agents-cancel-command.js +139 -0
  46. package/server/lib/agents-cancel-command.test.js +180 -0
  47. package/server/lib/agents-get-command.js +159 -0
  48. package/server/lib/agents-get-command.test.js +167 -0
  49. package/server/lib/agents-list-command.js +150 -0
  50. package/server/lib/agents-list-command.test.js +149 -0
  51. package/server/lib/agents-logs-command.js +126 -0
  52. package/server/lib/agents-logs-command.test.js +198 -0
  53. package/server/lib/agents-retry-command.js +117 -0
  54. package/server/lib/agents-retry-command.test.js +192 -0
  55. package/server/lib/budget-limits.js +222 -0
  56. package/server/lib/budget-limits.test.js +214 -0
  57. package/server/lib/code-generator.js +291 -0
  58. package/server/lib/code-generator.test.js +307 -0
  59. package/server/lib/cost-command.js +290 -0
  60. package/server/lib/cost-command.test.js +202 -0
  61. package/server/lib/cost-optimizer.js +404 -0
  62. package/server/lib/cost-optimizer.test.js +232 -0
  63. package/server/lib/cost-projections.js +302 -0
  64. package/server/lib/cost-projections.test.js +217 -0
  65. package/server/lib/cost-reports.js +277 -0
  66. package/server/lib/cost-reports.test.js +254 -0
  67. package/server/lib/cost-tracker.js +216 -0
  68. package/server/lib/cost-tracker.test.js +302 -0
  69. package/server/lib/crypto-patterns.js +433 -0
  70. package/server/lib/crypto-patterns.test.js +346 -0
  71. package/server/lib/design-command.js +385 -0
  72. package/server/lib/design-command.test.js +249 -0
  73. package/server/lib/design-parser.js +237 -0
  74. package/server/lib/design-parser.test.js +290 -0
  75. package/server/lib/gemini-vision.js +377 -0
  76. package/server/lib/gemini-vision.test.js +282 -0
  77. package/server/lib/input-validator.js +360 -0
  78. package/server/lib/input-validator.test.js +295 -0
  79. package/server/lib/litellm-client.js +232 -0
  80. package/server/lib/litellm-client.test.js +267 -0
  81. package/server/lib/litellm-command.js +291 -0
  82. package/server/lib/litellm-command.test.js +260 -0
  83. package/server/lib/litellm-config.js +273 -0
  84. package/server/lib/litellm-config.test.js +212 -0
  85. package/server/lib/model-pricing.js +189 -0
  86. package/server/lib/model-pricing.test.js +178 -0
  87. package/server/lib/models-command.js +223 -0
  88. package/server/lib/models-command.test.js +193 -0
  89. package/server/lib/optimize-command.js +197 -0
  90. package/server/lib/optimize-command.test.js +193 -0
  91. package/server/lib/orchestration-integration.js +206 -0
  92. package/server/lib/orchestration-integration.test.js +235 -0
  93. package/server/lib/output-encoder.js +308 -0
  94. package/server/lib/output-encoder.test.js +312 -0
  95. package/server/lib/quality-evaluator.js +396 -0
  96. package/server/lib/quality-evaluator.test.js +337 -0
  97. package/server/lib/quality-gate-command.js +340 -0
  98. package/server/lib/quality-gate-command.test.js +321 -0
  99. package/server/lib/quality-gate-scorer.js +378 -0
  100. package/server/lib/quality-gate-scorer.test.js +376 -0
  101. package/server/lib/quality-history.js +265 -0
  102. package/server/lib/quality-history.test.js +359 -0
  103. package/server/lib/quality-presets.js +288 -0
  104. package/server/lib/quality-presets.test.js +269 -0
  105. package/server/lib/quality-retry.js +323 -0
  106. package/server/lib/quality-retry.test.js +325 -0
  107. package/server/lib/quality-thresholds.js +255 -0
  108. package/server/lib/quality-thresholds.test.js +237 -0
  109. package/server/lib/secure-auth.js +333 -0
  110. package/server/lib/secure-auth.test.js +288 -0
  111. package/server/lib/secure-code-command.js +540 -0
  112. package/server/lib/secure-code-command.test.js +309 -0
  113. package/server/lib/secure-errors.js +521 -0
  114. package/server/lib/secure-errors.test.js +298 -0
  115. package/server/lib/vision-command.js +372 -0
  116. package/server/lib/vision-command.test.js +255 -0
  117. package/server/lib/visual-command.js +350 -0
  118. package/server/lib/visual-command.test.js +256 -0
  119. package/server/lib/visual-testing.js +315 -0
  120. package/server/lib/visual-testing.test.js +357 -0
  121. package/server/package-lock.json +2 -2
  122. package/server/package.json +1 -1
@@ -0,0 +1,273 @@
1
+ /**
2
+ * LiteLLM Config Module
3
+ *
4
+ * Configuration management for LiteLLM proxy
5
+ */
6
+
7
+ const DEFAULT_BASE_URL = 'http://localhost:4000';
8
+
9
+ /**
10
+ * Create a new LiteLLM configuration
11
+ * @returns {Object} Configuration object
12
+ */
13
+ function createConfig() {
14
+ return {
15
+ baseUrl: DEFAULT_BASE_URL,
16
+ models: {},
17
+ fallbacks: {},
18
+ spendLimits: {
19
+ daily: null,
20
+ monthly: null,
21
+ byModel: {},
22
+ byUser: {},
23
+ },
24
+ cache: {
25
+ enabled: false,
26
+ ttl: 3600,
27
+ },
28
+ };
29
+ }
30
+
31
+ /**
32
+ * Set a model alias
33
+ * @param {Object} config - Configuration object
34
+ * @param {Object} options - Alias options
35
+ * @param {string} options.alias - Logical name
36
+ * @param {string} options.provider - Provider name
37
+ * @param {string} options.model - Model identifier
38
+ */
39
+ function setModelAlias(config, options) {
40
+ const { alias, provider, model } = options;
41
+
42
+ config.models[alias] = {
43
+ provider,
44
+ model,
45
+ };
46
+ }
47
+
48
+ /**
49
+ * Get a model alias
50
+ * @param {Object} config - Configuration object
51
+ * @param {string} alias - Logical name
52
+ * @returns {Object|null} Model configuration or null
53
+ */
54
+ function getModelAlias(config, alias) {
55
+ return config.models[alias] || null;
56
+ }
57
+
58
+ /**
59
+ * Set fallback chain for a model
60
+ * @param {Object} config - Configuration object
61
+ * @param {Object} options - Fallback options
62
+ * @param {string} options.primary - Primary model
63
+ * @param {string[]} options.fallbacks - Ordered fallback models
64
+ */
65
+ function setFallbackChain(config, options) {
66
+ const { primary, fallbacks } = options;
67
+
68
+ config.fallbacks[primary] = fallbacks;
69
+ }
70
+
71
+ /**
72
+ * Get fallback chain for a model
73
+ * @param {Object} config - Configuration object
74
+ * @param {string} model - Model name
75
+ * @returns {string[]} Fallback models
76
+ */
77
+ function getFallbackChain(config, model) {
78
+ return config.fallbacks[model] || [];
79
+ }
80
+
81
+ /**
82
+ * Set spend limit
83
+ * @param {Object} config - Configuration object
84
+ * @param {Object} options - Limit options
85
+ * @param {string} options.type - Limit type: 'daily', 'monthly', 'model', 'user'
86
+ * @param {number} options.limit - Limit amount in dollars
87
+ * @param {string} [options.model] - Model name (for model limits)
88
+ * @param {string} [options.user] - User identifier (for user limits)
89
+ */
90
+ function setSpendLimit(config, options) {
91
+ const { type, limit, model, user } = options;
92
+
93
+ if (type === 'daily') {
94
+ config.spendLimits.daily = limit;
95
+ } else if (type === 'monthly') {
96
+ config.spendLimits.monthly = limit;
97
+ } else if (type === 'model' && model) {
98
+ config.spendLimits.byModel[model] = limit;
99
+ } else if (type === 'user' && user) {
100
+ config.spendLimits.byUser[user] = limit;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Get spend limit
106
+ * @param {Object} config - Configuration object
107
+ * @param {Object} options - Query options
108
+ * @param {string} options.type - Limit type
109
+ * @param {string} [options.model] - Model name
110
+ * @param {string} [options.user] - User identifier
111
+ * @returns {number|null} Limit or null
112
+ */
113
+ function getSpendLimit(config, options) {
114
+ const { type, model, user } = options;
115
+
116
+ if (type === 'daily') {
117
+ return config.spendLimits.daily;
118
+ } else if (type === 'monthly') {
119
+ return config.spendLimits.monthly;
120
+ } else if (type === 'model' && model) {
121
+ return config.spendLimits.byModel[model] || null;
122
+ } else if (type === 'user' && user) {
123
+ return config.spendLimits.byUser[user] || null;
124
+ }
125
+
126
+ return null;
127
+ }
128
+
129
+ /**
130
+ * Set base URL for LiteLLM proxy
131
+ * @param {Object} config - Configuration object
132
+ * @param {string} url - Base URL
133
+ */
134
+ function setBaseUrl(config, url) {
135
+ config.baseUrl = url;
136
+ }
137
+
138
+ /**
139
+ * Get base URL for LiteLLM proxy
140
+ * @param {Object} config - Configuration object
141
+ * @returns {string} Base URL
142
+ */
143
+ function getBaseUrl(config) {
144
+ return config.baseUrl;
145
+ }
146
+
147
+ /**
148
+ * Enable response caching
149
+ * @param {Object} config - Configuration object
150
+ * @param {Object} options - Cache options
151
+ * @param {number} [options.ttl=3600] - Cache TTL in seconds
152
+ */
153
+ function enableCache(config, options = {}) {
154
+ config.cache.enabled = true;
155
+ config.cache.ttl = options.ttl || 3600;
156
+ }
157
+
158
+ /**
159
+ * Disable response caching
160
+ * @param {Object} config - Configuration object
161
+ */
162
+ function disableCache(config) {
163
+ config.cache.enabled = false;
164
+ }
165
+
166
+ /**
167
+ * Check if caching is enabled
168
+ * @param {Object} config - Configuration object
169
+ * @returns {boolean} Cache enabled status
170
+ */
171
+ function isCacheEnabled(config) {
172
+ return config.cache.enabled;
173
+ }
174
+
175
+ /**
176
+ * Export configuration
177
+ * @param {Object} config - Configuration object
178
+ * @param {Object} options - Export options
179
+ * @param {string} [options.format='json'] - Export format: 'json' or 'yaml'
180
+ * @returns {string} Exported configuration
181
+ */
182
+ function exportConfig(config, options = {}) {
183
+ const { format = 'json' } = options;
184
+
185
+ if (format === 'yaml') {
186
+ // Simple YAML export (no external dependencies)
187
+ const lines = [];
188
+ lines.push(`base_url: "${config.baseUrl}"`);
189
+ lines.push('');
190
+ lines.push('models:');
191
+ for (const [alias, model] of Object.entries(config.models)) {
192
+ lines.push(` ${alias}:`);
193
+ lines.push(` provider: "${model.provider}"`);
194
+ lines.push(` model: "${model.model}"`);
195
+ }
196
+ lines.push('');
197
+ lines.push('fallbacks:');
198
+ for (const [primary, fallbacks] of Object.entries(config.fallbacks)) {
199
+ lines.push(` ${primary}: [${fallbacks.map(f => `"${f}"`).join(', ')}]`);
200
+ }
201
+ lines.push('');
202
+ lines.push('spend_limits:');
203
+ if (config.spendLimits.daily !== null) {
204
+ lines.push(` daily: ${config.spendLimits.daily}`);
205
+ }
206
+ if (config.spendLimits.monthly !== null) {
207
+ lines.push(` monthly: ${config.spendLimits.monthly}`);
208
+ }
209
+ lines.push('');
210
+ lines.push('cache:');
211
+ lines.push(` enabled: ${config.cache.enabled}`);
212
+ lines.push(` ttl: ${config.cache.ttl}`);
213
+
214
+ return lines.join('\n');
215
+ }
216
+
217
+ return JSON.stringify(config, null, 2);
218
+ }
219
+
220
+ /**
221
+ * Import configuration
222
+ * @param {string} data - Configuration data
223
+ * @param {Object} options - Import options
224
+ * @param {string} [options.format='json'] - Import format
225
+ * @returns {Object} Configuration object
226
+ */
227
+ function importConfig(data, options = {}) {
228
+ const { format = 'json' } = options;
229
+
230
+ if (format === 'json') {
231
+ const imported = JSON.parse(data);
232
+ const config = createConfig();
233
+
234
+ if (imported.models) {
235
+ config.models = imported.models;
236
+ }
237
+ if (imported.fallbacks) {
238
+ config.fallbacks = imported.fallbacks;
239
+ }
240
+ if (imported.baseUrl) {
241
+ config.baseUrl = imported.baseUrl;
242
+ }
243
+ if (imported.spendLimits) {
244
+ config.spendLimits = { ...config.spendLimits, ...imported.spendLimits };
245
+ }
246
+ if (imported.cache) {
247
+ config.cache = { ...config.cache, ...imported.cache };
248
+ }
249
+
250
+ return config;
251
+ }
252
+
253
+ // For YAML, we'd need a parser - return default config
254
+ return createConfig();
255
+ }
256
+
257
+ module.exports = {
258
+ createConfig,
259
+ setModelAlias,
260
+ getModelAlias,
261
+ setFallbackChain,
262
+ getFallbackChain,
263
+ setSpendLimit,
264
+ getSpendLimit,
265
+ setBaseUrl,
266
+ getBaseUrl,
267
+ enableCache,
268
+ disableCache,
269
+ isCacheEnabled,
270
+ exportConfig,
271
+ importConfig,
272
+ DEFAULT_BASE_URL,
273
+ };
@@ -0,0 +1,212 @@
1
+ /**
2
+ * LiteLLM Config Tests
3
+ *
4
+ * Configuration management for LiteLLM proxy
5
+ */
6
+
7
+ const { describe, it, beforeEach } = require('node:test');
8
+ const assert = require('node:assert');
9
+
10
+ const {
11
+ createConfig,
12
+ setModelAlias,
13
+ getModelAlias,
14
+ setFallbackChain,
15
+ getFallbackChain,
16
+ setSpendLimit,
17
+ getSpendLimit,
18
+ setBaseUrl,
19
+ getBaseUrl,
20
+ enableCache,
21
+ disableCache,
22
+ isCacheEnabled,
23
+ exportConfig,
24
+ importConfig,
25
+ } = require('./litellm-config.js');
26
+
27
+ describe('LiteLLM Config', () => {
28
+ let config;
29
+
30
+ beforeEach(() => {
31
+ config = createConfig();
32
+ });
33
+
34
+ describe('createConfig', () => {
35
+ it('creates default config', () => {
36
+ assert.ok(config);
37
+ assert.ok(config.models);
38
+ assert.ok(config.fallbacks);
39
+ });
40
+
41
+ it('has default base URL', () => {
42
+ const baseUrl = getBaseUrl(config);
43
+ assert.ok(baseUrl.includes('localhost') || baseUrl.includes('127.0.0.1'));
44
+ });
45
+ });
46
+
47
+ describe('setModelAlias', () => {
48
+ it('maps logical name to provider model', () => {
49
+ setModelAlias(config, {
50
+ alias: 'fast',
51
+ provider: 'anthropic',
52
+ model: 'claude-3-haiku-20240307',
53
+ });
54
+
55
+ const resolved = getModelAlias(config, 'fast');
56
+ assert.strictEqual(resolved.provider, 'anthropic');
57
+ assert.strictEqual(resolved.model, 'claude-3-haiku-20240307');
58
+ });
59
+
60
+ it('supports multiple aliases', () => {
61
+ setModelAlias(config, { alias: 'fast', provider: 'anthropic', model: 'claude-3-haiku-20240307' });
62
+ setModelAlias(config, { alias: 'smart', provider: 'anthropic', model: 'claude-3-opus-20240229' });
63
+
64
+ assert.ok(getModelAlias(config, 'fast'));
65
+ assert.ok(getModelAlias(config, 'smart'));
66
+ });
67
+ });
68
+
69
+ describe('getModelAlias', () => {
70
+ it('returns null for unknown alias', () => {
71
+ const resolved = getModelAlias(config, 'nonexistent');
72
+ assert.strictEqual(resolved, null);
73
+ });
74
+
75
+ it('resolves nested aliases', () => {
76
+ setModelAlias(config, { alias: 'default', provider: 'anthropic', model: 'claude-3-sonnet-20240229' });
77
+
78
+ const resolved = getModelAlias(config, 'default');
79
+ assert.ok(resolved);
80
+ });
81
+ });
82
+
83
+ describe('setFallbackChain', () => {
84
+ it('sets ordered fallback models', () => {
85
+ setFallbackChain(config, {
86
+ primary: 'claude-3-opus',
87
+ fallbacks: ['claude-3-sonnet', 'gpt-4', 'gemini-pro'],
88
+ });
89
+
90
+ const chain = getFallbackChain(config, 'claude-3-opus');
91
+ assert.deepStrictEqual(chain, ['claude-3-sonnet', 'gpt-4', 'gemini-pro']);
92
+ });
93
+
94
+ it('allows empty fallback', () => {
95
+ setFallbackChain(config, {
96
+ primary: 'claude-3-haiku',
97
+ fallbacks: [],
98
+ });
99
+
100
+ const chain = getFallbackChain(config, 'claude-3-haiku');
101
+ assert.deepStrictEqual(chain, []);
102
+ });
103
+ });
104
+
105
+ describe('getFallbackChain', () => {
106
+ it('returns empty array for unknown model', () => {
107
+ const chain = getFallbackChain(config, 'unknown-model');
108
+ assert.deepStrictEqual(chain, []);
109
+ });
110
+ });
111
+
112
+ describe('setSpendLimit', () => {
113
+ it('sets daily spend limit', () => {
114
+ setSpendLimit(config, {
115
+ type: 'daily',
116
+ limit: 50.00,
117
+ });
118
+
119
+ const limit = getSpendLimit(config, { type: 'daily' });
120
+ assert.strictEqual(limit, 50.00);
121
+ });
122
+
123
+ it('sets per-model spend limit', () => {
124
+ setSpendLimit(config, {
125
+ type: 'model',
126
+ model: 'claude-3-opus',
127
+ limit: 100.00,
128
+ });
129
+
130
+ const limit = getSpendLimit(config, { type: 'model', model: 'claude-3-opus' });
131
+ assert.strictEqual(limit, 100.00);
132
+ });
133
+
134
+ it('sets per-user spend limit', () => {
135
+ setSpendLimit(config, {
136
+ type: 'user',
137
+ user: 'developer-1',
138
+ limit: 25.00,
139
+ });
140
+
141
+ const limit = getSpendLimit(config, { type: 'user', user: 'developer-1' });
142
+ assert.strictEqual(limit, 25.00);
143
+ });
144
+ });
145
+
146
+ describe('getSpendLimit', () => {
147
+ it('returns null for unset limit', () => {
148
+ const limit = getSpendLimit(config, { type: 'daily' });
149
+ assert.strictEqual(limit, null);
150
+ });
151
+ });
152
+
153
+ describe('setBaseUrl', () => {
154
+ it('updates proxy URL', () => {
155
+ setBaseUrl(config, 'http://litellm.example.com:4000');
156
+
157
+ const url = getBaseUrl(config);
158
+ assert.strictEqual(url, 'http://litellm.example.com:4000');
159
+ });
160
+ });
161
+
162
+ describe('enableCache', () => {
163
+ it('enables response caching', () => {
164
+ enableCache(config, { ttl: 3600 });
165
+
166
+ assert.strictEqual(isCacheEnabled(config), true);
167
+ });
168
+ });
169
+
170
+ describe('disableCache', () => {
171
+ it('disables response caching', () => {
172
+ enableCache(config, { ttl: 3600 });
173
+ disableCache(config);
174
+
175
+ assert.strictEqual(isCacheEnabled(config), false);
176
+ });
177
+ });
178
+
179
+ describe('exportConfig', () => {
180
+ it('exports as YAML string', () => {
181
+ setModelAlias(config, { alias: 'fast', provider: 'anthropic', model: 'claude-3-haiku-20240307' });
182
+
183
+ const yaml = exportConfig(config, { format: 'yaml' });
184
+
185
+ assert.ok(typeof yaml === 'string');
186
+ assert.ok(yaml.includes('fast'));
187
+ });
188
+
189
+ it('exports as JSON string', () => {
190
+ setModelAlias(config, { alias: 'fast', provider: 'anthropic', model: 'claude-3-haiku-20240307' });
191
+
192
+ const json = exportConfig(config, { format: 'json' });
193
+
194
+ const parsed = JSON.parse(json);
195
+ assert.ok(parsed.models);
196
+ });
197
+ });
198
+
199
+ describe('importConfig', () => {
200
+ it('imports from JSON', () => {
201
+ const jsonConfig = JSON.stringify({
202
+ models: { fast: { provider: 'openai', model: 'gpt-4' } },
203
+ baseUrl: 'http://custom:8000',
204
+ });
205
+
206
+ const imported = importConfig(jsonConfig, { format: 'json' });
207
+
208
+ assert.ok(imported.models);
209
+ assert.strictEqual(getBaseUrl(imported), 'http://custom:8000');
210
+ });
211
+ });
212
+ });
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Model Pricing Module
3
+ *
4
+ * Pricing database for all supported models
5
+ */
6
+
7
+ const fs = require('fs');
8
+
9
+ /**
10
+ * Default pricing for known models (per 1K tokens)
11
+ * Prices are approximate and should be updated regularly
12
+ */
13
+ const DEFAULT_PRICING = {
14
+ // Anthropic Claude models
15
+ 'claude-3-opus': { inputPer1kTokens: 0.015, outputPer1kTokens: 0.075 },
16
+ 'claude-3-sonnet': { inputPer1kTokens: 0.003, outputPer1kTokens: 0.015 },
17
+ 'claude-3-haiku': { inputPer1kTokens: 0.00025, outputPer1kTokens: 0.00125 },
18
+ 'claude-3.5-sonnet': { inputPer1kTokens: 0.003, outputPer1kTokens: 0.015 },
19
+ 'claude-opus-4-5-20251101': { inputPer1kTokens: 0.015, outputPer1kTokens: 0.075 },
20
+
21
+ // OpenAI models
22
+ 'gpt-4': { inputPer1kTokens: 0.03, outputPer1kTokens: 0.06 },
23
+ 'gpt-4-turbo': { inputPer1kTokens: 0.01, outputPer1kTokens: 0.03 },
24
+ 'gpt-4o': { inputPer1kTokens: 0.005, outputPer1kTokens: 0.015 },
25
+ 'gpt-3.5-turbo': { inputPer1kTokens: 0.0005, outputPer1kTokens: 0.0015 },
26
+ 'o1': { inputPer1kTokens: 0.015, outputPer1kTokens: 0.06 },
27
+ 'o3': { inputPer1kTokens: 0.015, outputPer1kTokens: 0.06 },
28
+
29
+ // DeepSeek models
30
+ 'deepseek-r1': { inputPer1kTokens: 0.00055, outputPer1kTokens: 0.00219 },
31
+ 'deepseek-chat': { inputPer1kTokens: 0.00014, outputPer1kTokens: 0.00028 },
32
+ 'deepseek-coder': { inputPer1kTokens: 0.00014, outputPer1kTokens: 0.00028 },
33
+
34
+ // Google Gemini models
35
+ 'gemini-2.0-flash': { inputPer1kTokens: 0.00, outputPer1kTokens: 0.00 },
36
+ 'gemini-1.5-pro': { inputPer1kTokens: 0.00125, outputPer1kTokens: 0.005 },
37
+ 'gemini-1.5-flash': { inputPer1kTokens: 0.000075, outputPer1kTokens: 0.0003 },
38
+ };
39
+
40
+ // Runtime pricing (can be modified)
41
+ let runtimePricing = { ...DEFAULT_PRICING };
42
+
43
+ // Fallback rate for unknown models
44
+ const FALLBACK_RATE = { inputPer1kTokens: 0.01, outputPer1kTokens: 0.03 };
45
+
46
+ /**
47
+ * Get pricing for a model
48
+ * @param {string} model - Model name
49
+ * @returns {Object|null} Pricing object or null if unknown
50
+ */
51
+ function getPricing(model) {
52
+ return runtimePricing[model] || null;
53
+ }
54
+
55
+ /**
56
+ * Calculate cost from token counts
57
+ * @param {Object} options - Calculation options
58
+ * @param {string} options.model - Model name
59
+ * @param {number} options.inputTokens - Input token count
60
+ * @param {number} options.outputTokens - Output token count
61
+ * @param {boolean} [options.isLocal] - Whether this is a local model
62
+ * @param {boolean} [options.useFallback=true] - Use fallback pricing for unknown
63
+ * @returns {number} Cost in dollars
64
+ */
65
+ function calculateCost(options) {
66
+ const { model, inputTokens, outputTokens, isLocal, useFallback = true } = options;
67
+
68
+ // Local models are free
69
+ if (isLocal) {
70
+ return 0;
71
+ }
72
+
73
+ const pricing = getPricing(model);
74
+
75
+ if (!pricing) {
76
+ if (!useFallback) {
77
+ return 0;
78
+ }
79
+ // Use fallback rate
80
+ const inputCost = (inputTokens / 1000) * FALLBACK_RATE.inputPer1kTokens;
81
+ const outputCost = (outputTokens / 1000) * FALLBACK_RATE.outputPer1kTokens;
82
+ return inputCost + outputCost;
83
+ }
84
+
85
+ const inputCost = (inputTokens / 1000) * pricing.inputPer1kTokens;
86
+ const outputCost = (outputTokens / 1000) * pricing.outputPer1kTokens;
87
+
88
+ return inputCost + outputCost;
89
+ }
90
+
91
+ /**
92
+ * Load custom pricing from config file
93
+ * @param {string} filePath - Path to pricing config
94
+ * @param {Object} options - Options including fs module
95
+ * @returns {Object} Loaded pricing
96
+ */
97
+ function loadPricing(filePath, options = {}) {
98
+ const fsModule = options.fs || fs;
99
+
100
+ if (!fsModule.existsSync(filePath)) {
101
+ return { ...DEFAULT_PRICING };
102
+ }
103
+
104
+ try {
105
+ const custom = JSON.parse(fsModule.readFileSync(filePath, 'utf-8'));
106
+ return { ...DEFAULT_PRICING, ...custom };
107
+ } catch (err) {
108
+ return { ...DEFAULT_PRICING };
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Update pricing for a model at runtime
114
+ * @param {string} model - Model name
115
+ * @param {Object} pricing - Pricing object
116
+ * @param {number} pricing.inputPer1kTokens - Input cost per 1K tokens
117
+ * @param {number} pricing.outputPer1kTokens - Output cost per 1K tokens
118
+ */
119
+ function updatePricing(model, pricing) {
120
+ runtimePricing[model] = pricing;
121
+ }
122
+
123
+ /**
124
+ * Get all default pricing
125
+ * @returns {Object} Default pricing for all models
126
+ */
127
+ function getDefaultPricing() {
128
+ return { ...DEFAULT_PRICING };
129
+ }
130
+
131
+ /**
132
+ * Estimate cost using fallback rates
133
+ * @param {Object} options - Estimation options
134
+ * @param {string} options.model - Model name (may be unknown)
135
+ * @param {number} options.inputTokens - Input token count
136
+ * @param {number} options.outputTokens - Output token count
137
+ * @returns {number} Estimated cost
138
+ */
139
+ function estimateCost(options) {
140
+ const { model, inputTokens, outputTokens } = options;
141
+
142
+ const pricing = getPricing(model) || FALLBACK_RATE;
143
+
144
+ const inputCost = (inputTokens / 1000) * pricing.inputPer1kTokens;
145
+ const outputCost = (outputTokens / 1000) * pricing.outputPer1kTokens;
146
+
147
+ return inputCost + outputCost;
148
+ }
149
+
150
+ /**
151
+ * Format cost as currency string
152
+ * @param {number} cost - Cost in dollars
153
+ * @returns {string} Formatted cost string
154
+ */
155
+ function formatCost(cost) {
156
+ if (cost === 0) {
157
+ return '$0.00';
158
+ }
159
+
160
+ if (cost < 0.0001) {
161
+ return '<$0.0001';
162
+ }
163
+
164
+ if (cost < 0.01) {
165
+ return `$${cost.toFixed(4)}`;
166
+ }
167
+
168
+ return `$${cost.toFixed(2)}`;
169
+ }
170
+
171
+ /**
172
+ * Reset runtime pricing to defaults
173
+ */
174
+ function resetPricing() {
175
+ runtimePricing = { ...DEFAULT_PRICING };
176
+ }
177
+
178
+ module.exports = {
179
+ getPricing,
180
+ calculateCost,
181
+ loadPricing,
182
+ updatePricing,
183
+ getDefaultPricing,
184
+ estimateCost,
185
+ formatCost,
186
+ resetPricing,
187
+ DEFAULT_PRICING,
188
+ FALLBACK_RATE,
189
+ };