@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
package/src/config.ts ADDED
@@ -0,0 +1,254 @@
1
+ import { z } from 'zod';
2
+ import dotenv from 'dotenv';
3
+ import path from 'path';
4
+ import type { ProviderConfig, ModelInfo, BudgetConfig } from './types';
5
+
6
+ dotenv.config({ path: path.resolve(process.cwd(), '.env') });
7
+
8
+ // ---- Environment Schema ----
9
+
10
+ const envSchema = z.object({
11
+ PORT: z.coerce.number().default(3210),
12
+ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
13
+ ENCRYPTION_KEY: z.string().min(32).default('dev_key_change_in_production_32chars!!'),
14
+ DEFAULT_DAILY_BUDGET: z.coerce.number().positive().default(10),
15
+ DEFAULT_MONTHLY_BUDGET: z.coerce.number().positive().default(200),
16
+ DEFAULT_PER_REQUEST_CAP: z.coerce.number().positive().default(1),
17
+ CACHE_TTL: z.coerce.number().positive().default(3600),
18
+ RATE_LIMIT_WINDOW_MS: z.coerce.number().positive().default(60000),
19
+ RATE_LIMIT_MAX_REQUESTS: z.coerce.number().positive().default(60),
20
+ LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
21
+ OPENAI_API_KEY: z.string().optional(),
22
+ ANTHROPIC_API_KEY: z.string().optional(),
23
+ GOOGLE_API_KEY: z.string().optional(),
24
+ DEEPSEEK_API_KEY: z.string().optional(),
25
+ });
26
+
27
+ export type EnvConfig = z.infer<typeof envSchema>;
28
+
29
+ function parseEnv(): EnvConfig {
30
+ const result = envSchema.safeParse(process.env);
31
+ if (!result.success) {
32
+ const errors = result.error.issues
33
+ .map((i) => `${i.path.join('.')}: ${i.message}`)
34
+ .join('; ');
35
+ throw new Error(`Configuration error: ${errors}`);
36
+ }
37
+ return result.data;
38
+ }
39
+
40
+ export const env = parseEnv();
41
+
42
+ // ---- Provider Configurations ----
43
+
44
+ export function getProviders(): ProviderConfig[] {
45
+ return [
46
+ {
47
+ id: 'openai',
48
+ name: 'OpenAI',
49
+ baseUrl: 'https://api.openai.com/v1',
50
+ apiKey: env.OPENAI_API_KEY ?? '',
51
+ enabled: !!env.OPENAI_API_KEY,
52
+ priority: 1,
53
+ timeoutMs: 30000,
54
+ },
55
+ {
56
+ id: 'anthropic',
57
+ name: 'Anthropic',
58
+ baseUrl: 'https://api.anthropic.com/v1',
59
+ apiKey: env.ANTHROPIC_API_KEY ?? '',
60
+ enabled: !!env.ANTHROPIC_API_KEY,
61
+ priority: 2,
62
+ timeoutMs: 30000,
63
+ },
64
+ {
65
+ id: 'google',
66
+ name: 'Google',
67
+ baseUrl: 'https://generativelanguage.googleapis.com/v1beta',
68
+ apiKey: env.GOOGLE_API_KEY ?? '',
69
+ enabled: !!env.GOOGLE_API_KEY,
70
+ priority: 3,
71
+ timeoutMs: 30000,
72
+ },
73
+ {
74
+ id: 'deepseek',
75
+ name: 'DeepSeek',
76
+ baseUrl: 'https://api.deepseek.com/v1',
77
+ apiKey: env.DEEPSEEK_API_KEY ?? '',
78
+ enabled: !!env.DEEPSEEK_API_KEY,
79
+ priority: 4,
80
+ timeoutMs: 30000,
81
+ },
82
+ ];
83
+ }
84
+
85
+ // ---- Model Catalog ----
86
+
87
+ export const MODEL_CATALOG: ModelInfo[] = [
88
+ // OpenAI
89
+ {
90
+ id: 'gpt-4o',
91
+ provider: 'openai',
92
+ displayName: 'GPT-4o',
93
+ inputPricePer1M: 2.50,
94
+ outputPricePer1M: 10.00,
95
+ contextWindow: 128000,
96
+ maxOutputTokens: 16384,
97
+ capabilities: ['code', 'creative', 'reasoning', 'chat', 'analysis', 'math'],
98
+ qualityScore: 9,
99
+ speedScore: 7,
100
+ costScore: 5,
101
+ },
102
+ {
103
+ id: 'gpt-4o-mini',
104
+ provider: 'openai',
105
+ displayName: 'GPT-4o Mini',
106
+ inputPricePer1M: 0.15,
107
+ outputPricePer1M: 0.60,
108
+ contextWindow: 128000,
109
+ maxOutputTokens: 16384,
110
+ capabilities: ['code', 'chat', 'summarization', 'translation'],
111
+ qualityScore: 7,
112
+ speedScore: 9,
113
+ costScore: 9,
114
+ },
115
+ {
116
+ id: 'gpt-4-turbo',
117
+ provider: 'openai',
118
+ displayName: 'GPT-4 Turbo',
119
+ inputPricePer1M: 10.00,
120
+ outputPricePer1M: 30.00,
121
+ contextWindow: 128000,
122
+ maxOutputTokens: 4096,
123
+ capabilities: ['code', 'creative', 'reasoning', 'chat', 'analysis', 'math'],
124
+ qualityScore: 9,
125
+ speedScore: 6,
126
+ costScore: 3,
127
+ },
128
+ // Anthropic
129
+ {
130
+ id: 'claude-sonnet-4-20250514',
131
+ provider: 'anthropic',
132
+ displayName: 'Claude Sonnet 4',
133
+ inputPricePer1M: 3.00,
134
+ outputPricePer1M: 15.00,
135
+ contextWindow: 200000,
136
+ maxOutputTokens: 8192,
137
+ capabilities: ['code', 'creative', 'reasoning', 'chat', 'analysis'],
138
+ qualityScore: 9,
139
+ speedScore: 7,
140
+ costScore: 5,
141
+ },
142
+ {
143
+ id: 'claude-haiku-3-5-20241022',
144
+ provider: 'anthropic',
145
+ displayName: 'Claude 3.5 Haiku',
146
+ inputPricePer1M: 0.80,
147
+ outputPricePer1M: 4.00,
148
+ contextWindow: 200000,
149
+ maxOutputTokens: 8192,
150
+ capabilities: ['code', 'chat', 'summarization', 'translation'],
151
+ qualityScore: 7,
152
+ speedScore: 9,
153
+ costScore: 7,
154
+ },
155
+ // Google
156
+ {
157
+ id: 'gemini-2.0-flash',
158
+ provider: 'google',
159
+ displayName: 'Gemini 2.0 Flash',
160
+ inputPricePer1M: 0.10,
161
+ outputPricePer1M: 0.40,
162
+ contextWindow: 1048576,
163
+ maxOutputTokens: 8192,
164
+ capabilities: ['code', 'chat', 'summarization', 'translation', 'analysis', 'math'],
165
+ qualityScore: 7,
166
+ speedScore: 10,
167
+ costScore: 10,
168
+ },
169
+ {
170
+ id: 'gemini-2.0-flash-lite',
171
+ provider: 'google',
172
+ displayName: 'Gemini 2.0 Flash Lite',
173
+ inputPricePer1M: 0.075,
174
+ outputPricePer1M: 0.30,
175
+ contextWindow: 1048576,
176
+ maxOutputTokens: 8192,
177
+ capabilities: ['chat', 'summarization', 'translation'],
178
+ qualityScore: 6,
179
+ speedScore: 10,
180
+ costScore: 10,
181
+ },
182
+ // DeepSeek
183
+ {
184
+ id: 'deepseek-chat',
185
+ provider: 'deepseek',
186
+ displayName: 'DeepSeek V3',
187
+ inputPricePer1M: 0.27,
188
+ outputPricePer1M: 1.10,
189
+ contextWindow: 65536,
190
+ maxOutputTokens: 8192,
191
+ capabilities: ['code', 'chat', 'reasoning', 'analysis', 'math'],
192
+ qualityScore: 8,
193
+ speedScore: 8,
194
+ costScore: 8,
195
+ },
196
+ {
197
+ id: 'deepseek-reasoner',
198
+ provider: 'deepseek',
199
+ displayName: 'DeepSeek R1',
200
+ inputPricePer1M: 0.55,
201
+ outputPricePer1M: 2.19,
202
+ contextWindow: 65536,
203
+ maxOutputTokens: 8192,
204
+ capabilities: ['reasoning', 'math', 'code', 'analysis'],
205
+ qualityScore: 9,
206
+ speedScore: 5,
207
+ costScore: 7,
208
+ },
209
+ ];
210
+
211
+ // ---- Budget Defaults ----
212
+
213
+ export function getDefaultBudget(): BudgetConfig {
214
+ return {
215
+ dailyBudget: env.DEFAULT_DAILY_BUDGET,
216
+ monthlyBudget: env.DEFAULT_MONTHLY_BUDGET,
217
+ perRequestCap: env.DEFAULT_PER_REQUEST_CAP,
218
+ };
219
+ }
220
+
221
+ // ---- Database Path ----
222
+
223
+ export function getDbPath(): string {
224
+ return path.resolve(process.cwd(), 'data', 'budgetllm.db');
225
+ }
226
+
227
+ // ---- Logging Helper ----
228
+
229
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error';
230
+ const LOG_LEVELS: Record<LogLevel, number> = {
231
+ debug: 0,
232
+ info: 1,
233
+ warn: 2,
234
+ error: 3,
235
+ };
236
+
237
+ function shouldLog(level: LogLevel): boolean {
238
+ return LOG_LEVELS[level] >= LOG_LEVELS[env.LOG_LEVEL];
239
+ }
240
+
241
+ export const log = {
242
+ debug: (msg: string, data?: unknown) => {
243
+ if (shouldLog('debug')) console.debug(`[DEBUG] ${msg}`, data ?? '');
244
+ },
245
+ info: (msg: string, data?: unknown) => {
246
+ if (shouldLog('info')) console.info(`[INFO] ${msg}`, data ?? '');
247
+ },
248
+ warn: (msg: string, data?: unknown) => {
249
+ if (shouldLog('warn')) console.warn(`[WARN] ${msg}`, data ?? '');
250
+ },
251
+ error: (msg: string, data?: unknown) => {
252
+ if (shouldLog('error')) console.error(`[ERROR] ${msg}`, data ?? '');
253
+ },
254
+ };