@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.
Files changed (65) hide show
  1. package/.env.example +21 -0
  2. package/LICENSE +21 -0
  3. package/README.md +293 -0
  4. package/dist/config.d.ts +77 -0
  5. package/dist/config.d.ts.map +1 -0
  6. package/dist/config.js +246 -0
  7. package/dist/config.js.map +1 -0
  8. package/dist/database.d.ts +24 -0
  9. package/dist/database.d.ts.map +1 -0
  10. package/dist/database.js +414 -0
  11. package/dist/database.js.map +1 -0
  12. package/dist/providers.d.ts +20 -0
  13. package/dist/providers.d.ts.map +1 -0
  14. package/dist/providers.js +208 -0
  15. package/dist/providers.js.map +1 -0
  16. package/dist/proxy.d.ts +7 -0
  17. package/dist/proxy.d.ts.map +1 -0
  18. package/dist/proxy.js +181 -0
  19. package/dist/proxy.js.map +1 -0
  20. package/dist/rate-limiter.d.ts +8 -0
  21. package/dist/rate-limiter.d.ts.map +1 -0
  22. package/dist/rate-limiter.js +72 -0
  23. package/dist/rate-limiter.js.map +1 -0
  24. package/dist/router.d.ts +33 -0
  25. package/dist/router.d.ts.map +1 -0
  26. package/dist/router.js +186 -0
  27. package/dist/router.js.map +1 -0
  28. package/dist/server.d.ts +3 -0
  29. package/dist/server.d.ts.map +1 -0
  30. package/dist/server.js +705 -0
  31. package/dist/server.js.map +1 -0
  32. package/dist/task-classifier.d.ts +4 -0
  33. package/dist/task-classifier.d.ts.map +1 -0
  34. package/dist/task-classifier.js +123 -0
  35. package/dist/task-classifier.js.map +1 -0
  36. package/dist/types.d.ts +205 -0
  37. package/dist/types.d.ts.map +1 -0
  38. package/dist/types.js +46 -0
  39. package/dist/types.js.map +1 -0
  40. package/dist/utils/encryption.d.ts +4 -0
  41. package/dist/utils/encryption.d.ts.map +1 -0
  42. package/dist/utils/encryption.js +40 -0
  43. package/dist/utils/encryption.js.map +1 -0
  44. package/package.json +63 -0
  45. package/src/config.ts +254 -0
  46. package/src/database.ts +496 -0
  47. package/src/providers.ts +315 -0
  48. package/src/proxy.ts +226 -0
  49. package/src/rate-limiter.ts +81 -0
  50. package/src/router.ts +228 -0
  51. package/src/server.ts +754 -0
  52. package/src/task-classifier.ts +134 -0
  53. package/src/types/sql.js.d.ts +27 -0
  54. package/src/types.ts +258 -0
  55. package/src/utils/encryption.ts +36 -0
  56. package/tests/config.test.ts +85 -0
  57. package/tests/database.test.ts +194 -0
  58. package/tests/encryption.test.ts +57 -0
  59. package/tests/rate-limiter.test.ts +83 -0
  60. package/tests/router.test.ts +182 -0
  61. package/tests/server.test.ts +253 -0
  62. package/tests/setup.ts +15 -0
  63. package/tests/task-classifier.test.ts +117 -0
  64. package/tsconfig.json +25 -0
  65. 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":";;;;;AAiBA,wCA6BC;AA2ND,gDAiDC;AA1TD,kDAA0B;AAC1B,gDAAwB;AAExB,qCAA4D;AAU5D;;;GAGG;AACI,KAAK,UAAU,cAAc,CAClC,UAAsB,EACtB,KAAa,EACb,QAAuB,EACvB,OAIC;IAED,MAAM,SAAS,GAAG,IAAA,qBAAY,GAAE,CAAC;IACjC,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,UAAU,CAAC,CAAC;IAE5D,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,YAAY,UAAU,mBAAmB,CAAC,CAAC;IAC7D,CAAC;IAED,QAAQ,UAAU,EAAE,CAAC;QACnB,KAAK,QAAQ;YACX,OAAO,UAAU,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;QACxD,KAAK,WAAW;YACd,OAAO,aAAa,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC3D,KAAK,QAAQ;YACX,OAAO,UAAU,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;QACxD,KAAK,UAAU;YACb,OAAO,YAAY,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC1D;YACE,MAAM,IAAI,KAAK,CAAC,qBAAqB,UAAU,EAAE,CAAC,CAAC;IACvD,CAAC;AACH,CAAC;AAED,kDAAkD;AAElD,KAAK,UAAU,UAAU,CACvB,QAAwB,EACxB,KAAa,EACb,QAAuB,EACvB,OAAsE;IAEtE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;QAC1B,KAAK;QACL,QAAQ;QACR,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,GAAG;QACvC,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,IAAI;QACtC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;KAC1B,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,MAAM,WAAW,CAC9B,GAAG,QAAQ,CAAC,OAAO,mBAAmB,EACtC,MAAM,EACN,IAAI,EACJ;QACE,eAAe,EAAE,UAAU,QAAQ,CAAC,MAAM,EAAE;QAC5C,cAAc,EAAE,kBAAkB;KACnC,EACD,QAAQ,CAAC,SAAS,CACnB,CAAC;IAEF,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAI7B,CAAC;IAEF,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,IAAI,EAAE;QAChD,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,WAAW,EAAE,IAAI,CAAC,KAAK,EAAE,aAAa,IAAI,CAAC;QAC3C,YAAY,EAAE,IAAI,CAAC,KAAK,EAAE,iBAAiB,IAAI,CAAC;QAChD,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,aAAa,IAAI,MAAM;KACvD,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,YAAY,CACzB,QAAwB,EACxB,KAAa,EACb,QAAuB,EACvB,OAAsE;IAEtE,sCAAsC;IACtC,OAAO,UAAU,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;AACxD,CAAC;AAED,sBAAsB;AAEtB,KAAK,UAAU,aAAa,CAC1B,QAAwB,EACxB,KAAa,EACb,QAAuB,EACvB,OAAsE;IAEtE,iEAAiE;IACjE,MAAM,aAAa,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,EAAE,OAAO,IAAI,EAAE,CAAC;IAC/E,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;IAEjE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;QAC1B,KAAK;QACL,QAAQ,EAAE,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QACzE,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnD,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,IAAI;QACtC,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,GAAG;QACvC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;KAC1B,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,MAAM,WAAW,CAC9B,GAAG,QAAQ,CAAC,OAAO,WAAW,EAC9B,MAAM,EACN,IAAI,EACJ;QACE,WAAW,EAAE,QAAQ,CAAC,MAAM;QAC5B,mBAAmB,EAAE,YAAY;QACjC,cAAc,EAAE,kBAAkB;KACnC,EACD,QAAQ,CAAC,SAAS,CACnB,CAAC;IAEF,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAK7B,CAAC;IAEF,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,OAAO;YACnB,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;aACjC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;aAClB,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE;QACjB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,WAAW,EAAE,IAAI,CAAC,KAAK,EAAE,YAAY,IAAI,CAAC;QAC1C,YAAY,EAAE,IAAI,CAAC,KAAK,EAAE,aAAa,IAAI,CAAC;QAC5C,YAAY,EAAE,IAAI,CAAC,WAAW,IAAI,UAAU;KAC7C,CAAC;AACJ,CAAC;AAED,0BAA0B;AAE1B,KAAK,UAAU,UAAU,CACvB,QAAwB,EACxB,KAAa,EACb,QAAuB,EACvB,OAAsE;IAEtE,oCAAoC;IACpC,MAAM,QAAQ,GAAG,QAAQ;SACtB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC;SAClC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACX,IAAI,EAAE,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM;QAC/C,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;KAC7B,CAAC,CAAC,CAAC;IAEN,MAAM,iBAAiB,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;IACpE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;QAC1B,QAAQ;QACR,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrG,gBAAgB,EAAE;YAChB,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,GAAG;YACvC,eAAe,EAAE,OAAO,CAAC,UAAU,IAAI,IAAI;YAC3C,IAAI,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;SACzB;KACF,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,MAAM,WAAW,CAC9B,GAAG,QAAQ,CAAC,OAAO,WAAW,KAAK,wBAAwB,QAAQ,CAAC,MAAM,EAAE,EAC5E,MAAM,EACN,IAAI,EACJ;QACE,cAAc,EAAE,kBAAkB;KACnC,EACD,QAAQ,CAAC,SAAS,CACnB,CAAC;IAEF,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAO7B,CAAC;IAEF,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE;QAChF,KAAK;QACL,WAAW,EAAE,IAAI,CAAC,aAAa,EAAE,gBAAgB,IAAI,CAAC;QACtD,YAAY,EAAE,IAAI,CAAC,aAAa,EAAE,oBAAoB,IAAI,CAAC;QAC3D,YAAY,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,IAAI,MAAM;KAC3D,CAAC;AACJ,CAAC;AAED,wBAAwB;AAExB,SAAS,WAAW,CAClB,GAAW,EACX,MAAc,EACd,IAAY,EACZ,OAA+B,EAC/B,SAAiB;IAEjB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,OAAO,GAAG,SAAS,CAAC,QAAQ,KAAK,QAAQ,CAAC;QAChD,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,eAAK,CAAC,CAAC,CAAC,cAAI,CAAC;QAEnC,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CACrB;YACE,QAAQ,EAAE,SAAS,CAAC,QAAQ;YAC5B,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5C,IAAI,EAAE,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC,MAAM;YAC3C,MAAM;YACN,OAAO,EAAE;gBACP,GAAG,OAAO;gBACV,gBAAgB,EAAE,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;aACrD;YACD,OAAO,EAAE,SAAS;SACnB,EACD,CAAC,GAAG,EAAE,EAAE;YACN,IAAI,IAAI,GAAG,EAAE,CAAC;YACd,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;gBAC/B,IAAI,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YAC3B,CAAC,CAAC,CAAC;YACH,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;gBACjB,IAAI,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,UAAU,IAAI,GAAG,EAAE,CAAC;oBAC5C,YAAG,CAAC,KAAK,CAAC,qBAAqB,GAAG,CAAC,UAAU,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;oBAC5E,MAAM,CAAC,IAAI,KAAK,CAAC,0BAA0B,GAAG,CAAC,UAAU,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC3F,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,IAAI,CAAC,CAAC;gBAChB,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CACF,CAAC;QAEF,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACtB,MAAM,CAAC,IAAI,KAAK,CAAC,kBAAkB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;QAEH,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;YACrB,GAAG,CAAC,OAAO,EAAE,CAAC;YACd,MAAM,CAAC,IAAI,KAAK,CAAC,2BAA2B,SAAS,IAAI,CAAC,CAAC,CAAC;QAC9D,CAAC,CAAC,CAAC;QAEH,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChB,GAAG,CAAC,GAAG,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,6BAA6B;AAE7B,SAAgB,kBAAkB,CAChC,gBAAkC,EAClC,UAAsB,EACtB,SAAiB;IAEjB,MAAM,SAAS,GAAG,sBAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAC7E,MAAM,SAAS,GAAG,SAAS;QACzB,CAAC,CAAC,CAAC,gBAAgB,CAAC,WAAW,GAAG,OAAO,CAAC,GAAG,SAAS,CAAC,eAAe;QACtE,CAAC,CAAC,CAAC,CAAC;IACN,MAAM,UAAU,GAAG,SAAS;QAC1B,CAAC,CAAC,CAAC,gBAAgB,CAAC,YAAY,GAAG,OAAO,CAAC,GAAG,SAAS,CAAC,gBAAgB;QACxE,CAAC,CAAC,CAAC,CAAC;IAEN,8BAA8B;IAC9B,MAAM,aAAa,GAAG,CAAC,gBAAgB,CAAC,WAAW,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IACpE,MAAM,cAAc,GAAG,CAAC,gBAAgB,CAAC,YAAY,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IACtE,MAAM,SAAS,GAAG,aAAa,GAAG,cAAc,CAAC;IACjD,MAAM,WAAW,GAAG,SAAS,GAAG,UAAU,CAAC;IAE3C,OAAO;QACL,EAAE,EAAE,SAAS;QACb,MAAM,EAAE,iBAAiB;QACzB,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;QACtC,KAAK,EAAE,gBAAgB,CAAC,KAAK;QAC7B,OAAO,EAAE;YACP;gBACE,KAAK,EAAE,CAAC;gBACR,OAAO,EAAE;oBACP,IAAI,EAAE,WAAW;oBACjB,OAAO,EAAE,gBAAgB,CAAC,OAAO;iBAClC;gBACD,aAAa,EAAE,gBAAgB,CAAC,YAAY;aAC7C;SACF;QACD,KAAK,EAAE;YACL,aAAa,EAAE,gBAAgB,CAAC,WAAW;YAC3C,iBAAiB,EAAE,gBAAgB,CAAC,YAAY;YAChD,YAAY,EAAE,gBAAgB,CAAC,WAAW,GAAG,gBAAgB,CAAC,YAAY;SAC3E;QACD,IAAI,EAAE;YACJ,SAAS;YACT,UAAU;YACV,SAAS,EAAE,WAAW;YACtB,QAAQ,EAAE,KAAK;YACf,KAAK,EAAE,gBAAgB,CAAC,KAAK;YAC7B,QAAQ,EAAE,UAAU;YACpB,aAAa,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,GAAG,WAAW,CAAC;SACpD;KACF,CAAC;AACJ,CAAC"}
@@ -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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"}
@@ -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"}