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.
- package/CLAUDE.md +23 -0
- package/CODING-STANDARDS.md +408 -0
- package/bin/install.js +2 -0
- package/dashboard/dist/components/QualityGatePane.d.ts +38 -0
- package/dashboard/dist/components/QualityGatePane.js +31 -0
- package/dashboard/dist/components/QualityGatePane.test.d.ts +1 -0
- package/dashboard/dist/components/QualityGatePane.test.js +147 -0
- package/dashboard/dist/components/orchestration/AgentCard.d.ts +26 -0
- package/dashboard/dist/components/orchestration/AgentCard.js +60 -0
- package/dashboard/dist/components/orchestration/AgentCard.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentCard.test.js +63 -0
- package/dashboard/dist/components/orchestration/AgentControls.d.ts +11 -0
- package/dashboard/dist/components/orchestration/AgentControls.js +20 -0
- package/dashboard/dist/components/orchestration/AgentControls.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentControls.test.js +52 -0
- package/dashboard/dist/components/orchestration/AgentDetail.d.ts +35 -0
- package/dashboard/dist/components/orchestration/AgentDetail.js +37 -0
- package/dashboard/dist/components/orchestration/AgentDetail.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentDetail.test.js +79 -0
- package/dashboard/dist/components/orchestration/AgentList.d.ts +31 -0
- package/dashboard/dist/components/orchestration/AgentList.js +47 -0
- package/dashboard/dist/components/orchestration/AgentList.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentList.test.js +64 -0
- package/dashboard/dist/components/orchestration/CostMeter.d.ts +11 -0
- package/dashboard/dist/components/orchestration/CostMeter.js +28 -0
- package/dashboard/dist/components/orchestration/CostMeter.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/CostMeter.test.js +50 -0
- package/dashboard/dist/components/orchestration/ModelSelector.d.ts +20 -0
- package/dashboard/dist/components/orchestration/ModelSelector.js +12 -0
- package/dashboard/dist/components/orchestration/ModelSelector.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/ModelSelector.test.js +56 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.d.ts +28 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.js +28 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.js +56 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.d.ts +11 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.js +37 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.test.js +52 -0
- package/dashboard/dist/components/orchestration/index.d.ts +8 -0
- package/dashboard/dist/components/orchestration/index.js +8 -0
- package/package.json +1 -1
- package/server/lib/access-control.js +352 -0
- package/server/lib/access-control.test.js +322 -0
- package/server/lib/agents-cancel-command.js +139 -0
- package/server/lib/agents-cancel-command.test.js +180 -0
- package/server/lib/agents-get-command.js +159 -0
- package/server/lib/agents-get-command.test.js +167 -0
- package/server/lib/agents-list-command.js +150 -0
- package/server/lib/agents-list-command.test.js +149 -0
- package/server/lib/agents-logs-command.js +126 -0
- package/server/lib/agents-logs-command.test.js +198 -0
- package/server/lib/agents-retry-command.js +117 -0
- package/server/lib/agents-retry-command.test.js +192 -0
- package/server/lib/budget-limits.js +222 -0
- package/server/lib/budget-limits.test.js +214 -0
- package/server/lib/code-generator.js +291 -0
- package/server/lib/code-generator.test.js +307 -0
- package/server/lib/cost-command.js +290 -0
- package/server/lib/cost-command.test.js +202 -0
- package/server/lib/cost-optimizer.js +404 -0
- package/server/lib/cost-optimizer.test.js +232 -0
- package/server/lib/cost-projections.js +302 -0
- package/server/lib/cost-projections.test.js +217 -0
- package/server/lib/cost-reports.js +277 -0
- package/server/lib/cost-reports.test.js +254 -0
- package/server/lib/cost-tracker.js +216 -0
- package/server/lib/cost-tracker.test.js +302 -0
- package/server/lib/crypto-patterns.js +433 -0
- package/server/lib/crypto-patterns.test.js +346 -0
- package/server/lib/design-command.js +385 -0
- package/server/lib/design-command.test.js +249 -0
- package/server/lib/design-parser.js +237 -0
- package/server/lib/design-parser.test.js +290 -0
- package/server/lib/gemini-vision.js +377 -0
- package/server/lib/gemini-vision.test.js +282 -0
- package/server/lib/input-validator.js +360 -0
- package/server/lib/input-validator.test.js +295 -0
- package/server/lib/litellm-client.js +232 -0
- package/server/lib/litellm-client.test.js +267 -0
- package/server/lib/litellm-command.js +291 -0
- package/server/lib/litellm-command.test.js +260 -0
- package/server/lib/litellm-config.js +273 -0
- package/server/lib/litellm-config.test.js +212 -0
- package/server/lib/model-pricing.js +189 -0
- package/server/lib/model-pricing.test.js +178 -0
- package/server/lib/models-command.js +223 -0
- package/server/lib/models-command.test.js +193 -0
- package/server/lib/optimize-command.js +197 -0
- package/server/lib/optimize-command.test.js +193 -0
- package/server/lib/orchestration-integration.js +206 -0
- package/server/lib/orchestration-integration.test.js +235 -0
- package/server/lib/output-encoder.js +308 -0
- package/server/lib/output-encoder.test.js +312 -0
- package/server/lib/quality-evaluator.js +396 -0
- package/server/lib/quality-evaluator.test.js +337 -0
- package/server/lib/quality-gate-command.js +340 -0
- package/server/lib/quality-gate-command.test.js +321 -0
- package/server/lib/quality-gate-scorer.js +378 -0
- package/server/lib/quality-gate-scorer.test.js +376 -0
- package/server/lib/quality-history.js +265 -0
- package/server/lib/quality-history.test.js +359 -0
- package/server/lib/quality-presets.js +288 -0
- package/server/lib/quality-presets.test.js +269 -0
- package/server/lib/quality-retry.js +323 -0
- package/server/lib/quality-retry.test.js +325 -0
- package/server/lib/quality-thresholds.js +255 -0
- package/server/lib/quality-thresholds.test.js +237 -0
- package/server/lib/secure-auth.js +333 -0
- package/server/lib/secure-auth.test.js +288 -0
- package/server/lib/secure-code-command.js +540 -0
- package/server/lib/secure-code-command.test.js +309 -0
- package/server/lib/secure-errors.js +521 -0
- package/server/lib/secure-errors.test.js +298 -0
- package/server/lib/vision-command.js +372 -0
- package/server/lib/vision-command.test.js +255 -0
- package/server/lib/visual-command.js +350 -0
- package/server/lib/visual-command.test.js +256 -0
- package/server/lib/visual-testing.js +315 -0
- package/server/lib/visual-testing.test.js +357 -0
- package/server/package-lock.json +2 -2
- 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
|
+
};
|