@theihtisham/budget-llm 1.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.
- package/.env.example +21 -0
- package/LICENSE +21 -0
- package/README.md +293 -0
- package/dist/config.d.ts +77 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +246 -0
- package/dist/config.js.map +1 -0
- package/dist/database.d.ts +24 -0
- package/dist/database.d.ts.map +1 -0
- package/dist/database.js +414 -0
- package/dist/database.js.map +1 -0
- package/dist/providers.d.ts +20 -0
- package/dist/providers.d.ts.map +1 -0
- package/dist/providers.js +208 -0
- package/dist/providers.js.map +1 -0
- package/dist/proxy.d.ts +7 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +181 -0
- package/dist/proxy.js.map +1 -0
- package/dist/rate-limiter.d.ts +8 -0
- package/dist/rate-limiter.d.ts.map +1 -0
- package/dist/rate-limiter.js +72 -0
- package/dist/rate-limiter.js.map +1 -0
- package/dist/router.d.ts +33 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +186 -0
- package/dist/router.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +705 -0
- package/dist/server.js.map +1 -0
- package/dist/task-classifier.d.ts +4 -0
- package/dist/task-classifier.d.ts.map +1 -0
- package/dist/task-classifier.js +123 -0
- package/dist/task-classifier.js.map +1 -0
- package/dist/types.d.ts +205 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +46 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/encryption.d.ts +4 -0
- package/dist/utils/encryption.d.ts.map +1 -0
- package/dist/utils/encryption.js +40 -0
- package/dist/utils/encryption.js.map +1 -0
- package/package.json +63 -0
- package/src/config.ts +254 -0
- package/src/database.ts +496 -0
- package/src/providers.ts +315 -0
- package/src/proxy.ts +226 -0
- package/src/rate-limiter.ts +81 -0
- package/src/router.ts +228 -0
- package/src/server.ts +754 -0
- package/src/task-classifier.ts +134 -0
- package/src/types/sql.js.d.ts +27 -0
- package/src/types.ts +258 -0
- package/src/utils/encryption.ts +36 -0
- package/tests/config.test.ts +85 -0
- package/tests/database.test.ts +194 -0
- package/tests/encryption.test.ts +57 -0
- package/tests/rate-limiter.test.ts +83 -0
- package/tests/router.test.ts +182 -0
- package/tests/server.test.ts +253 -0
- package/tests/setup.ts +15 -0
- package/tests/task-classifier.test.ts +117 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.sendToProvider = sendToProvider;
|
|
7
|
+
exports.buildProxyResponse = buildProxyResponse;
|
|
8
|
+
const https_1 = __importDefault(require("https"));
|
|
9
|
+
const http_1 = __importDefault(require("http"));
|
|
10
|
+
const config_1 = require("./config");
|
|
11
|
+
/**
|
|
12
|
+
* Send a chat completion request to a specific provider.
|
|
13
|
+
* Translates the unified request format into the provider's native API format.
|
|
14
|
+
*/
|
|
15
|
+
async function sendToProvider(providerId, model, messages, options) {
|
|
16
|
+
const providers = (0, config_1.getProviders)();
|
|
17
|
+
const provider = providers.find((p) => p.id === providerId);
|
|
18
|
+
if (!provider || !provider.enabled) {
|
|
19
|
+
throw new Error(`Provider ${providerId} is not available`);
|
|
20
|
+
}
|
|
21
|
+
switch (providerId) {
|
|
22
|
+
case 'openai':
|
|
23
|
+
return sendOpenAI(provider, model, messages, options);
|
|
24
|
+
case 'anthropic':
|
|
25
|
+
return sendAnthropic(provider, model, messages, options);
|
|
26
|
+
case 'google':
|
|
27
|
+
return sendGoogle(provider, model, messages, options);
|
|
28
|
+
case 'deepseek':
|
|
29
|
+
return sendDeepSeek(provider, model, messages, options);
|
|
30
|
+
default:
|
|
31
|
+
throw new Error(`Unknown provider: ${providerId}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// ---- OpenAI Compatible (OpenAI + DeepSeek) ----
|
|
35
|
+
async function sendOpenAI(provider, model, messages, options) {
|
|
36
|
+
const body = JSON.stringify({
|
|
37
|
+
model,
|
|
38
|
+
messages,
|
|
39
|
+
temperature: options.temperature ?? 0.7,
|
|
40
|
+
max_tokens: options.max_tokens ?? 2048,
|
|
41
|
+
top_p: options.top_p ?? 1,
|
|
42
|
+
});
|
|
43
|
+
const result = await makeRequest(`${provider.baseUrl}/chat/completions`, 'POST', body, {
|
|
44
|
+
'Authorization': `Bearer ${provider.apiKey}`,
|
|
45
|
+
'Content-Type': 'application/json',
|
|
46
|
+
}, provider.timeoutMs);
|
|
47
|
+
const data = JSON.parse(result);
|
|
48
|
+
return {
|
|
49
|
+
content: data.choices[0]?.message?.content ?? '',
|
|
50
|
+
model: data.model,
|
|
51
|
+
inputTokens: data.usage?.prompt_tokens ?? 0,
|
|
52
|
+
outputTokens: data.usage?.completion_tokens ?? 0,
|
|
53
|
+
finishReason: data.choices[0]?.finish_reason ?? 'stop',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async function sendDeepSeek(provider, model, messages, options) {
|
|
57
|
+
// DeepSeek uses OpenAI-compatible API
|
|
58
|
+
return sendOpenAI(provider, model, messages, options);
|
|
59
|
+
}
|
|
60
|
+
// ---- Anthropic ----
|
|
61
|
+
async function sendAnthropic(provider, model, messages, options) {
|
|
62
|
+
// Anthropic requires system message separate from messages array
|
|
63
|
+
const systemMessage = messages.find((m) => m.role === 'system')?.content ?? '';
|
|
64
|
+
const chatMessages = messages.filter((m) => m.role !== 'system');
|
|
65
|
+
const body = JSON.stringify({
|
|
66
|
+
model,
|
|
67
|
+
messages: chatMessages.map((m) => ({ role: m.role, content: m.content })),
|
|
68
|
+
...(systemMessage ? { system: systemMessage } : {}),
|
|
69
|
+
max_tokens: options.max_tokens ?? 2048,
|
|
70
|
+
temperature: options.temperature ?? 0.7,
|
|
71
|
+
top_p: options.top_p ?? 1,
|
|
72
|
+
});
|
|
73
|
+
const result = await makeRequest(`${provider.baseUrl}/messages`, 'POST', body, {
|
|
74
|
+
'x-api-key': provider.apiKey,
|
|
75
|
+
'anthropic-version': '2023-06-01',
|
|
76
|
+
'Content-Type': 'application/json',
|
|
77
|
+
}, provider.timeoutMs);
|
|
78
|
+
const data = JSON.parse(result);
|
|
79
|
+
return {
|
|
80
|
+
content: data.content
|
|
81
|
+
?.filter((c) => c.type === 'text')
|
|
82
|
+
.map((c) => c.text)
|
|
83
|
+
.join('') ?? '',
|
|
84
|
+
model: data.model,
|
|
85
|
+
inputTokens: data.usage?.input_tokens ?? 0,
|
|
86
|
+
outputTokens: data.usage?.output_tokens ?? 0,
|
|
87
|
+
finishReason: data.stop_reason ?? 'end_turn',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// ---- Google Gemini ----
|
|
91
|
+
async function sendGoogle(provider, model, messages, options) {
|
|
92
|
+
// Convert messages to Gemini format
|
|
93
|
+
const contents = messages
|
|
94
|
+
.filter((m) => m.role !== 'system')
|
|
95
|
+
.map((m) => ({
|
|
96
|
+
role: m.role === 'assistant' ? 'model' : 'user',
|
|
97
|
+
parts: [{ text: m.content }],
|
|
98
|
+
}));
|
|
99
|
+
const systemInstruction = messages.find((m) => m.role === 'system');
|
|
100
|
+
const body = JSON.stringify({
|
|
101
|
+
contents,
|
|
102
|
+
...(systemInstruction ? { systemInstruction: { parts: [{ text: systemInstruction.content }] } } : {}),
|
|
103
|
+
generationConfig: {
|
|
104
|
+
temperature: options.temperature ?? 0.7,
|
|
105
|
+
maxOutputTokens: options.max_tokens ?? 2048,
|
|
106
|
+
topP: options.top_p ?? 1,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
const result = await makeRequest(`${provider.baseUrl}/models/${model}:generateContent?key=${provider.apiKey}`, 'POST', body, {
|
|
110
|
+
'Content-Type': 'application/json',
|
|
111
|
+
}, provider.timeoutMs);
|
|
112
|
+
const data = JSON.parse(result);
|
|
113
|
+
return {
|
|
114
|
+
content: data.candidates?.[0]?.content?.parts?.map((p) => p.text).join('') ?? '',
|
|
115
|
+
model,
|
|
116
|
+
inputTokens: data.usageMetadata?.promptTokenCount ?? 0,
|
|
117
|
+
outputTokens: data.usageMetadata?.candidatesTokenCount ?? 0,
|
|
118
|
+
finishReason: data.candidates?.[0]?.finishReason ?? 'STOP',
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// ---- HTTP Helper ----
|
|
122
|
+
function makeRequest(url, method, body, headers, timeoutMs) {
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
const parsedUrl = new URL(url);
|
|
125
|
+
const isHttps = parsedUrl.protocol === 'https:';
|
|
126
|
+
const lib = isHttps ? https_1.default : http_1.default;
|
|
127
|
+
const req = lib.request({
|
|
128
|
+
hostname: parsedUrl.hostname,
|
|
129
|
+
port: parsedUrl.port || (isHttps ? 443 : 80),
|
|
130
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
131
|
+
method,
|
|
132
|
+
headers: {
|
|
133
|
+
...headers,
|
|
134
|
+
'Content-Length': Buffer.byteLength(body).toString(),
|
|
135
|
+
},
|
|
136
|
+
timeout: timeoutMs,
|
|
137
|
+
}, (res) => {
|
|
138
|
+
let data = '';
|
|
139
|
+
res.on('data', (chunk) => {
|
|
140
|
+
data += chunk.toString();
|
|
141
|
+
});
|
|
142
|
+
res.on('end', () => {
|
|
143
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
144
|
+
config_1.log.error(`Provider returned ${res.statusCode}: ${data.substring(0, 200)}`);
|
|
145
|
+
reject(new Error(`Provider returned HTTP ${res.statusCode}: ${data.substring(0, 500)}`));
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
resolve(data);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
req.on('error', (err) => {
|
|
153
|
+
reject(new Error(`Network error: ${err.message}`));
|
|
154
|
+
});
|
|
155
|
+
req.on('timeout', () => {
|
|
156
|
+
req.destroy();
|
|
157
|
+
reject(new Error(`Request timed out after ${timeoutMs}ms`));
|
|
158
|
+
});
|
|
159
|
+
req.write(body);
|
|
160
|
+
req.end();
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
// ---- Response Builder ----
|
|
164
|
+
function buildProxyResponse(providerResponse, providerId, requestId) {
|
|
165
|
+
const modelInfo = config_1.MODEL_CATALOG.find((m) => m.id === providerResponse.model);
|
|
166
|
+
const inputCost = modelInfo
|
|
167
|
+
? (providerResponse.inputTokens / 1000000) * modelInfo.inputPricePer1M
|
|
168
|
+
: 0;
|
|
169
|
+
const outputCost = modelInfo
|
|
170
|
+
? (providerResponse.outputTokens / 1000000) * modelInfo.outputPricePer1M
|
|
171
|
+
: 0;
|
|
172
|
+
// Compare against GPT-4 Turbo
|
|
173
|
+
const gpt4InputCost = (providerResponse.inputTokens / 1000000) * 10;
|
|
174
|
+
const gpt4OutputCost = (providerResponse.outputTokens / 1000000) * 30;
|
|
175
|
+
const gpt4Total = gpt4InputCost + gpt4OutputCost;
|
|
176
|
+
const actualTotal = inputCost + outputCost;
|
|
177
|
+
return {
|
|
178
|
+
id: requestId,
|
|
179
|
+
object: 'chat.completion',
|
|
180
|
+
created: Math.floor(Date.now() / 1000),
|
|
181
|
+
model: providerResponse.model,
|
|
182
|
+
choices: [
|
|
183
|
+
{
|
|
184
|
+
index: 0,
|
|
185
|
+
message: {
|
|
186
|
+
role: 'assistant',
|
|
187
|
+
content: providerResponse.content,
|
|
188
|
+
},
|
|
189
|
+
finish_reason: providerResponse.finishReason,
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
usage: {
|
|
193
|
+
prompt_tokens: providerResponse.inputTokens,
|
|
194
|
+
completion_tokens: providerResponse.outputTokens,
|
|
195
|
+
total_tokens: providerResponse.inputTokens + providerResponse.outputTokens,
|
|
196
|
+
},
|
|
197
|
+
cost: {
|
|
198
|
+
inputCost,
|
|
199
|
+
outputCost,
|
|
200
|
+
totalCost: actualTotal,
|
|
201
|
+
currency: 'USD',
|
|
202
|
+
model: providerResponse.model,
|
|
203
|
+
provider: providerId,
|
|
204
|
+
savingsVsGpt4: Math.max(0, gpt4Total - actualTotal),
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=providers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"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"}
|
package/dist/proxy.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ProxyRequest, ProxyResponse } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Main proxy handler: receives a request, classifies, routes, calls provider,
|
|
4
|
+
* tracks cost, handles caching and fallbacks.
|
|
5
|
+
*/
|
|
6
|
+
export declare function handleProxyRequest(req: ProxyRequest, clientIp: string): Promise<ProxyResponse>;
|
|
7
|
+
//# sourceMappingURL=proxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EAId,MAAM,SAAS,CAAC;AAejB;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,YAAY,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,aAAa,CAAC,CAoMxB"}
|
package/dist/proxy.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleProxyRequest = handleProxyRequest;
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
5
|
+
const types_1 = require("./types");
|
|
6
|
+
const config_1 = require("./config");
|
|
7
|
+
const router_1 = require("./router");
|
|
8
|
+
const task_classifier_1 = require("./task-classifier");
|
|
9
|
+
const providers_1 = require("./providers");
|
|
10
|
+
const database_1 = require("./database");
|
|
11
|
+
const encryption_1 = require("./utils/encryption");
|
|
12
|
+
const rate_limiter_1 = require("./rate-limiter");
|
|
13
|
+
/**
|
|
14
|
+
* Main proxy handler: receives a request, classifies, routes, calls provider,
|
|
15
|
+
* tracks cost, handles caching and fallbacks.
|
|
16
|
+
*/
|
|
17
|
+
async function handleProxyRequest(req, clientIp) {
|
|
18
|
+
const requestId = req.request_id || (0, uuid_1.v4)();
|
|
19
|
+
const startTime = Date.now();
|
|
20
|
+
// 1. Rate limit check
|
|
21
|
+
const rateLimitResult = (0, rate_limiter_1.checkRateLimit)(clientIp);
|
|
22
|
+
if (!rateLimitResult.allowed) {
|
|
23
|
+
throw new types_1.RateLimitError(rateLimitResult.resetMs);
|
|
24
|
+
}
|
|
25
|
+
// 2. Validate messages
|
|
26
|
+
if (!req.messages || req.messages.length === 0) {
|
|
27
|
+
throw new Error('messages array is required and must not be empty');
|
|
28
|
+
}
|
|
29
|
+
for (const msg of req.messages) {
|
|
30
|
+
if (!msg.role || !msg.content) {
|
|
31
|
+
throw new Error('Each message must have a role and content');
|
|
32
|
+
}
|
|
33
|
+
if (!['system', 'user', 'assistant'].includes(msg.role)) {
|
|
34
|
+
throw new Error(`Invalid message role: ${msg.role}`);
|
|
35
|
+
}
|
|
36
|
+
// Truncate very long content to prevent abuse
|
|
37
|
+
if (msg.content.length > 500000) {
|
|
38
|
+
throw new Error('Message content exceeds maximum length of 500,000 characters');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// 3. Budget check
|
|
42
|
+
const budgetViolation = (0, router_1.checkBudget)(req.budget_cap);
|
|
43
|
+
if (budgetViolation) {
|
|
44
|
+
const budget = (0, config_1.getDefaultBudget)();
|
|
45
|
+
const status = (0, database_1.getBudgetStatus)(budget);
|
|
46
|
+
const spent = budgetViolation === 'daily' ? status.daily.spent : status.monthly.spent;
|
|
47
|
+
const limit = budgetViolation === 'daily' ? status.daily.limit : status.monthly.limit;
|
|
48
|
+
throw new types_1.BudgetExceededError(budgetViolation, spent, limit);
|
|
49
|
+
}
|
|
50
|
+
// 4. Classify task type
|
|
51
|
+
const taskType = req.task_type || (0, task_classifier_1.classifyTask)(req.messages);
|
|
52
|
+
config_1.log.debug(`Request ${requestId}: classified as "${taskType}"`);
|
|
53
|
+
// 5. Check cache (only for deterministic-like requests)
|
|
54
|
+
const cacheKey = (0, encryption_1.hashPrompt)(JSON.stringify({
|
|
55
|
+
messages: req.messages,
|
|
56
|
+
model: req.model,
|
|
57
|
+
temperature: req.temperature ?? 0.7,
|
|
58
|
+
max_tokens: req.max_tokens ?? 2048,
|
|
59
|
+
}));
|
|
60
|
+
const cached = (0, database_1.getCacheEntry)(cacheKey);
|
|
61
|
+
if (cached) {
|
|
62
|
+
config_1.log.info(`Request ${requestId}: cache HIT (${cached.model})`);
|
|
63
|
+
const latency = Date.now() - startTime;
|
|
64
|
+
const costRecord = {
|
|
65
|
+
id: (0, uuid_1.v4)(),
|
|
66
|
+
requestId,
|
|
67
|
+
timestamp: new Date().toISOString(),
|
|
68
|
+
provider: cached.provider,
|
|
69
|
+
model: cached.model,
|
|
70
|
+
taskType,
|
|
71
|
+
inputTokens: cached.inputTokens,
|
|
72
|
+
outputTokens: cached.outputTokens,
|
|
73
|
+
inputCost: 0, // Cached = free
|
|
74
|
+
outputCost: 0,
|
|
75
|
+
totalCost: 0,
|
|
76
|
+
latencyMs: latency,
|
|
77
|
+
cached: true,
|
|
78
|
+
};
|
|
79
|
+
(0, database_1.insertCostRecord)(costRecord);
|
|
80
|
+
return {
|
|
81
|
+
id: requestId,
|
|
82
|
+
object: 'chat.completion',
|
|
83
|
+
created: Math.floor(Date.now() / 1000),
|
|
84
|
+
model: cached.model,
|
|
85
|
+
choices: [
|
|
86
|
+
{
|
|
87
|
+
index: 0,
|
|
88
|
+
message: { role: 'assistant', content: cached.response },
|
|
89
|
+
finish_reason: 'stop',
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
usage: {
|
|
93
|
+
prompt_tokens: cached.inputTokens,
|
|
94
|
+
completion_tokens: cached.outputTokens,
|
|
95
|
+
total_tokens: cached.inputTokens + cached.outputTokens,
|
|
96
|
+
},
|
|
97
|
+
cost: {
|
|
98
|
+
inputCost: 0,
|
|
99
|
+
outputCost: 0,
|
|
100
|
+
totalCost: 0,
|
|
101
|
+
currency: 'USD',
|
|
102
|
+
model: cached.model,
|
|
103
|
+
provider: cached.provider,
|
|
104
|
+
savingsVsGpt4: cached.cost,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// 6. Route to best model
|
|
109
|
+
const routing = (0, router_1.route)({
|
|
110
|
+
taskType,
|
|
111
|
+
messages: req.messages,
|
|
112
|
+
requestedModel: req.model,
|
|
113
|
+
budgetCap: req.budget_cap,
|
|
114
|
+
});
|
|
115
|
+
config_1.log.info(`Request ${requestId}: routed to ${routing.provider}/${routing.model} — ${routing.reason}`);
|
|
116
|
+
// 7. Send to provider with fallback chain
|
|
117
|
+
const fallbacks = (0, router_1.getFallbackChain)(routing.provider, taskType);
|
|
118
|
+
const allProviders = [
|
|
119
|
+
{ provider: routing.provider, model: routing.model },
|
|
120
|
+
...fallbacks,
|
|
121
|
+
];
|
|
122
|
+
for (const attempt of allProviders) {
|
|
123
|
+
try {
|
|
124
|
+
config_1.log.debug(`Request ${requestId}: trying ${attempt.provider}/${attempt.model}`);
|
|
125
|
+
const providerResponse = await (0, providers_1.sendToProvider)(attempt.provider, attempt.model, req.messages, {
|
|
126
|
+
temperature: req.temperature,
|
|
127
|
+
max_tokens: req.max_tokens,
|
|
128
|
+
top_p: req.top_p,
|
|
129
|
+
});
|
|
130
|
+
const latency = Date.now() - startTime;
|
|
131
|
+
const response = (0, providers_1.buildProxyResponse)(providerResponse, attempt.provider, requestId);
|
|
132
|
+
// 8. Per-request budget cap check
|
|
133
|
+
const budget = (0, config_1.getDefaultBudget)();
|
|
134
|
+
if (response.cost.totalCost > budget.perRequestCap) {
|
|
135
|
+
config_1.log.warn(`Request ${requestId}: cost $${response.cost.totalCost.toFixed(4)} exceeds per-request cap $${budget.perRequestCap}`);
|
|
136
|
+
// Still return the response but log the warning
|
|
137
|
+
}
|
|
138
|
+
// 9. Store cost record
|
|
139
|
+
const costRecord = {
|
|
140
|
+
id: (0, uuid_1.v4)(),
|
|
141
|
+
requestId,
|
|
142
|
+
timestamp: new Date().toISOString(),
|
|
143
|
+
provider: attempt.provider,
|
|
144
|
+
model: providerResponse.model,
|
|
145
|
+
taskType,
|
|
146
|
+
inputTokens: providerResponse.inputTokens,
|
|
147
|
+
outputTokens: providerResponse.outputTokens,
|
|
148
|
+
inputCost: response.cost.inputCost,
|
|
149
|
+
outputCost: response.cost.outputCost,
|
|
150
|
+
totalCost: response.cost.totalCost,
|
|
151
|
+
latencyMs: latency,
|
|
152
|
+
cached: false,
|
|
153
|
+
};
|
|
154
|
+
(0, database_1.insertCostRecord)(costRecord);
|
|
155
|
+
// 10. Cache the response
|
|
156
|
+
const cacheEntry = {
|
|
157
|
+
promptHash: cacheKey,
|
|
158
|
+
model: providerResponse.model,
|
|
159
|
+
provider: attempt.provider,
|
|
160
|
+
response: providerResponse.content,
|
|
161
|
+
inputTokens: providerResponse.inputTokens,
|
|
162
|
+
outputTokens: providerResponse.outputTokens,
|
|
163
|
+
cost: response.cost.totalCost,
|
|
164
|
+
createdAt: Date.now(),
|
|
165
|
+
expiresAt: Date.now() + config_1.env.CACHE_TTL * 1000,
|
|
166
|
+
hitCount: 0,
|
|
167
|
+
};
|
|
168
|
+
(0, database_1.setCacheEntry)(cacheEntry);
|
|
169
|
+
config_1.log.info(`Request ${requestId}: completed in ${latency}ms, cost=$${response.cost.totalCost.toFixed(4)}, saved=$${response.cost.savingsVsGpt4.toFixed(4)}`);
|
|
170
|
+
return response;
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
174
|
+
config_1.log.warn(`Request ${requestId}: ${attempt.provider}/${attempt.model} failed — ${error.message}`);
|
|
175
|
+
// Continue to next fallback
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// All providers failed
|
|
179
|
+
throw new types_1.NoProviderAvailableError();
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RateLimitStatus } from './types';
|
|
2
|
+
export declare function checkRateLimit(identifier: string): RateLimitStatus;
|
|
3
|
+
export declare function resetRateLimit(identifier: string): void;
|
|
4
|
+
export declare function getBucketStatus(identifier: string): {
|
|
5
|
+
tokens: number;
|
|
6
|
+
maxTokens: number;
|
|
7
|
+
} | null;
|
|
8
|
+
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,eAAe,EAAE,MAAM,SAAS,CAAC;AA+B5D,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe,CAyBlE;AAED,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAEvD;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAQhG"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkRateLimit = checkRateLimit;
|
|
4
|
+
exports.resetRateLimit = resetRateLimit;
|
|
5
|
+
exports.getBucketStatus = getBucketStatus;
|
|
6
|
+
const config_1 = require("./config");
|
|
7
|
+
const buckets = new Map();
|
|
8
|
+
function createBucket(_identifier) {
|
|
9
|
+
const maxTokens = config_1.env.RATE_LIMIT_MAX_REQUESTS;
|
|
10
|
+
const windowMs = config_1.env.RATE_LIMIT_WINDOW_MS;
|
|
11
|
+
const refillRate = maxTokens / (windowMs / 1000);
|
|
12
|
+
return {
|
|
13
|
+
bucket: {
|
|
14
|
+
tokens: maxTokens,
|
|
15
|
+
maxTokens,
|
|
16
|
+
refillRate,
|
|
17
|
+
lastRefill: Date.now(),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function refill(bucket) {
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
const elapsed = (now - bucket.lastRefill) / 1000;
|
|
24
|
+
bucket.tokens = Math.min(bucket.maxTokens, bucket.tokens + elapsed * bucket.refillRate);
|
|
25
|
+
bucket.lastRefill = now;
|
|
26
|
+
}
|
|
27
|
+
function checkRateLimit(identifier) {
|
|
28
|
+
let state = buckets.get(identifier);
|
|
29
|
+
if (!state) {
|
|
30
|
+
state = createBucket(identifier);
|
|
31
|
+
buckets.set(identifier, state);
|
|
32
|
+
}
|
|
33
|
+
const bucket = state.bucket;
|
|
34
|
+
refill(bucket);
|
|
35
|
+
if (bucket.tokens >= 1) {
|
|
36
|
+
bucket.tokens -= 1;
|
|
37
|
+
return {
|
|
38
|
+
allowed: true,
|
|
39
|
+
remaining: Math.floor(bucket.tokens),
|
|
40
|
+
resetMs: Math.ceil((1 - bucket.tokens) / bucket.refillRate * 1000),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const resetMs = Math.ceil((1 - bucket.tokens) / bucket.refillRate * 1000);
|
|
44
|
+
return {
|
|
45
|
+
allowed: false,
|
|
46
|
+
remaining: 0,
|
|
47
|
+
resetMs: Math.max(resetMs, 1000),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function resetRateLimit(identifier) {
|
|
51
|
+
buckets.delete(identifier);
|
|
52
|
+
}
|
|
53
|
+
function getBucketStatus(identifier) {
|
|
54
|
+
const state = buckets.get(identifier);
|
|
55
|
+
if (!state)
|
|
56
|
+
return null;
|
|
57
|
+
refill(state.bucket);
|
|
58
|
+
return {
|
|
59
|
+
tokens: Math.floor(state.bucket.tokens),
|
|
60
|
+
maxTokens: state.bucket.maxTokens,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
// Clean stale buckets every 5 minutes
|
|
64
|
+
setInterval(() => {
|
|
65
|
+
const cutoff = Date.now() - 300000;
|
|
66
|
+
for (const [key, state] of buckets) {
|
|
67
|
+
if (state.bucket.lastRefill < cutoff) {
|
|
68
|
+
buckets.delete(key);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}, 300000).unref();
|
|
72
|
+
//# sourceMappingURL=rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"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"}
|
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { TaskType, RoutingDecision, ProviderId, ChatMessage } from './types';
|
|
2
|
+
interface RoutingContext {
|
|
3
|
+
taskType: TaskType;
|
|
4
|
+
messages: ChatMessage[];
|
|
5
|
+
requestedModel?: string;
|
|
6
|
+
budgetCap?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Pick the best model for a request based on task type, cost, and quality.
|
|
10
|
+
*
|
|
11
|
+
* Strategy:
|
|
12
|
+
* - code tasks: prioritize cost-score with quality floor of 7
|
|
13
|
+
* - creative tasks: prioritize quality-score
|
|
14
|
+
* - reasoning/math: prioritize quality-score with cost awareness
|
|
15
|
+
* - chat/summarization/translation: prioritize cost-score with quality floor of 6
|
|
16
|
+
* - analysis: balance quality and cost
|
|
17
|
+
*/
|
|
18
|
+
export declare function route(context: RoutingContext): RoutingDecision;
|
|
19
|
+
/**
|
|
20
|
+
* Get a fallback chain of providers for a given model.
|
|
21
|
+
* Returns alternative models in priority order.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getFallbackChain(primaryProvider: ProviderId, taskType: TaskType): Array<{
|
|
24
|
+
provider: ProviderId;
|
|
25
|
+
model: string;
|
|
26
|
+
}>;
|
|
27
|
+
/**
|
|
28
|
+
* Check if a request would exceed budget limits.
|
|
29
|
+
* Returns the type of limit exceeded or null if within budget.
|
|
30
|
+
*/
|
|
31
|
+
export declare function checkBudget(budgetCap?: number): 'daily' | 'monthly' | 'per_request' | null;
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EAER,eAAe,EACf,UAAU,EACV,WAAW,EACZ,MAAM,SAAS,CAAC;AAWjB,UAAU,cAAc;IACtB,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,cAAc,GAAG,eAAe,CA2D9D;AAuFD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,eAAe,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,GAAG,KAAK,CAAC;IAAE,QAAQ,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAkBhI;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,aAAa,GAAG,IAAI,CAmB1F"}
|