@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/.env.example ADDED
@@ -0,0 +1,21 @@
1
+ # Server
2
+ PORT=3210
3
+ NODE_ENV=development
4
+
5
+ # Encryption key for API key storage (generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")
6
+ ENCRYPTION_KEY=change_me_to_a_64_char_hex_string_in_production_at_least_32_bytes_here
7
+
8
+ # Default budget limits (in USD)
9
+ DEFAULT_DAILY_BUDGET=10.00
10
+ DEFAULT_MONTHLY_BUDGET=200.00
11
+ DEFAULT_PER_REQUEST_CAP=1.00
12
+
13
+ # Cache TTL in seconds
14
+ CACHE_TTL=3600
15
+
16
+ # Rate limiting
17
+ RATE_LIMIT_WINDOW_MS=60000
18
+ RATE_LIMIT_MAX_REQUESTS=60
19
+
20
+ # Logging level: debug, info, warn, error
21
+ LOG_LEVEL=info
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 BudgetLLM Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,293 @@
1
+ # BudgetLLM
2
+
3
+ > Cut your AI costs by 60% — one API endpoint that automatically picks the cheapest model for every request.
4
+
5
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18-green.svg)](https://nodejs.org)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Strict-3178C6.svg)](https://www.typescriptlang.org/)
8
+ [![Vitest](https://img.shields.io/badge/tested%20with-Vitest-6E9F18.svg)](https://vitest.dev/)
9
+
10
+ BudgetLLM is a smart multi-provider LLM cost optimizer and routing proxy. It sits between your application and LLM providers (OpenAI, Anthropic, Google, DeepSeek) and automatically routes each request to the cheapest capable model based on the task type.
11
+
12
+ ## How It Works
13
+
14
+ ```
15
+ Your App
16
+ |
17
+ | POST /v1/chat/completions (OpenAI-compatible)
18
+ v
19
+ +------------------+
20
+ | BudgetLLM |
21
+ | Proxy Server |
22
+ +------------------+
23
+ | | | |
24
+ | Task | Budget | Cache | Rate Limit
25
+ | Classification | Enforcement | Lookup | Check
26
+ | | | |
27
+ v v v v
28
+ +----------------------------------------------------------+
29
+ | Smart Router |
30
+ | code tasks -> cheapest capable model |
31
+ | creative tasks -> highest quality model |
32
+ | chat tasks -> fastest cheapest model |
33
+ | reasoning -> best reasoning model |
34
+ +----------------------------------------------------------+
35
+ | | | |
36
+ v v v v
37
+ +--------+ +--------+ +--------+ +--------+
38
+ | OpenAI | |Anthropic| | Google | |DeepSeek|
39
+ +--------+ +--------+ +--------+ +--------+
40
+ | | | |
41
+ +-------------+------+------+-------------+
42
+ |
43
+ Cost Tracking
44
+ & Response Cache
45
+ |
46
+ v
47
+ SQLite Database
48
+ ```
49
+
50
+ ## Features
51
+
52
+ - **Smart Routing** - Automatically classifies tasks (code, creative, reasoning, chat, etc.) and picks the cheapest model that can handle it well
53
+ - **Budget Limits** - Daily, monthly, and per-request cost caps with automatic enforcement
54
+ - **Fallback Chain** - If the primary provider fails, automatically tries the next best provider
55
+ - **Cost Tracking** - Per-request, per-day, per-model cost logging with SQLite storage
56
+ - **Response Caching** - Identical prompts return cached responses (free!) with configurable TTL
57
+ - **Rate Limiting** - Token bucket algorithm with per-IP limits
58
+ - **Real-time Dashboard** - Beautiful web UI showing costs, savings, usage, and budget status
59
+ - **OpenAI-Compatible** - Drop-in replacement for the OpenAI API. Just change the base URL.
60
+ - **Security** - API key encryption (AES-256-GCM), rate limiting, input validation, no prompt content logging
61
+
62
+ ## Cost Comparison
63
+
64
+ BudgetLLM automatically picks the cheapest model for each task type. Here's how costs compare:
65
+
66
+ | Task Type | Default Route | Cost/1M Tokens | vs GPT-4 Turbo | Savings |
67
+ |-----------|--------------|----------------|-----------------|---------|
68
+ | Chat | Gemini 2.0 Flash | $0.10 / $0.40 | $10 / $30 | **97%** |
69
+ | Code | GPT-4o Mini | $0.15 / $0.60 | $10 / $30 | **98%** |
70
+ | Creative | GPT-4o | $2.50 / $10.00 | $10 / $30 | **67%** |
71
+ | Reasoning | DeepSeek R1 | $0.55 / $2.19 | $10 / $30 | **93%** |
72
+ | Summarization | Gemini 2.0 Flash | $0.10 / $0.40 | $10 / $30 | **97%** |
73
+ | Translation | Gemini 2.0 Flash Lite | $0.075 / $0.30 | $10 / $30 | **99%** |
74
+ | Math | DeepSeek R1 | $0.55 / $2.19 | $10 / $30 | **93%** |
75
+ | Analysis | DeepSeek V3 | $0.27 / $1.10 | $10 / $30 | **96%** |
76
+
77
+ ### Model Catalog
78
+
79
+ | Model | Provider | Input/1M | Output/1M | Quality | Speed | Cost |
80
+ |-------|----------|----------|-----------|---------|-------|------|
81
+ | GPT-4o | OpenAI | $2.50 | $10.00 | 9/10 | 7/10 | 5/10 |
82
+ | GPT-4o Mini | OpenAI | $0.15 | $0.60 | 7/10 | 9/10 | 9/10 |
83
+ | GPT-4 Turbo | OpenAI | $10.00 | $30.00 | 9/10 | 6/10 | 3/10 |
84
+ | Claude Sonnet 4 | Anthropic | $3.00 | $15.00 | 9/10 | 7/10 | 5/10 |
85
+ | Claude 3.5 Haiku | Anthropic | $0.80 | $4.00 | 7/10 | 9/10 | 7/10 |
86
+ | Gemini 2.0 Flash | Google | $0.10 | $0.40 | 7/10 | 10/10 | 10/10 |
87
+ | Gemini 2.0 Flash Lite | Google | $0.075 | $0.30 | 6/10 | 10/10 | 10/10 |
88
+ | DeepSeek V3 | DeepSeek | $0.27 | $1.10 | 8/10 | 8/10 | 8/10 |
89
+ | DeepSeek R1 | DeepSeek | $0.55 | $2.19 | 9/10 | 5/10 | 7/10 |
90
+
91
+ ## Quick Start
92
+
93
+ ### 1. Install
94
+
95
+ ```bash
96
+ git clone https://github.com/your-username/budget-llm.git
97
+ cd budget-llm
98
+ npm install
99
+ ```
100
+
101
+ ### 2. Configure
102
+
103
+ ```bash
104
+ cp .env.example .env
105
+ ```
106
+
107
+ Edit `.env` and add your API keys:
108
+
109
+ ```env
110
+ # Add at least one provider API key
111
+ OPENAI_API_KEY=sk-...
112
+ ANTHROPIC_API_KEY=sk-ant-...
113
+ GOOGLE_API_KEY=AIza...
114
+ DEEPSEEK_API_KEY=sk-...
115
+
116
+ # Set your budget limits
117
+ DEFAULT_DAILY_BUDGET=10.00
118
+ DEFAULT_MONTHLY_BUDGET=200.00
119
+ DEFAULT_PER_REQUEST_CAP=1.00
120
+ ```
121
+
122
+ ### 3. Run
123
+
124
+ ```bash
125
+ # Development
126
+ npm run dev
127
+
128
+ # Production
129
+ npm run build
130
+ npm start
131
+ ```
132
+
133
+ ### 4. Use It
134
+
135
+ BudgetLLM is a drop-in replacement for the OpenAI API. Just change the base URL:
136
+
137
+ **Before (direct OpenAI):**
138
+ ```javascript
139
+ const openai = new OpenAI({
140
+ apiKey: 'sk-your-key',
141
+ baseURL: 'https://api.openai.com/v1',
142
+ });
143
+ ```
144
+
145
+ **After (via BudgetLLM):**
146
+ ```javascript
147
+ const openai = new OpenAI({
148
+ apiKey: 'anything', // BudgetLLM doesn't require a client key
149
+ baseURL: 'http://localhost:3210/v1',
150
+ });
151
+ ```
152
+
153
+ That's it. Every request is now automatically optimized for cost.
154
+
155
+ ### 5. Optional: Task Type Hints
156
+
157
+ Help BudgetLLM pick the best model by specifying a task type:
158
+
159
+ ```javascript
160
+ const response = await openai.chat.completions.create({
161
+ model: 'auto', // let BudgetLLM decide
162
+ messages: [{ role: 'user', content: 'Write a sort function' }],
163
+ // BudgetLLM extension:
164
+ task_type: 'code', // forces code-optimized routing
165
+ });
166
+ ```
167
+
168
+ ## API Reference
169
+
170
+ ### OpenAI-Compatible Endpoints
171
+
172
+ | Method | Endpoint | Description |
173
+ |--------|----------|-------------|
174
+ | POST | `/v1/chat/completions` | Chat completion (drop-in replacement) |
175
+ | GET | `/v1/models` | List available models |
176
+
177
+ ### BudgetLLM Extensions
178
+
179
+ The `/v1/chat/completions` endpoint accepts these additional fields:
180
+
181
+ | Field | Type | Description |
182
+ |-------|------|-------------|
183
+ | `task_type` | string | Force routing: `code`, `creative`, `reasoning`, `chat`, `summarization`, `translation`, `analysis`, `math`, `auto` |
184
+ | `request_id` | string | Custom request ID for tracking |
185
+ | `budget_cap` | number | Per-request budget cap in USD |
186
+
187
+ The response includes a `cost` field:
188
+
189
+ ```json
190
+ {
191
+ "cost": {
192
+ "inputCost": 0.0000015,
193
+ "outputCost": 0.000006,
194
+ "totalCost": 0.0000075,
195
+ "currency": "USD",
196
+ "model": "gpt-4o-mini",
197
+ "provider": "openai",
198
+ "savingsVsGpt4": 0.00012
199
+ }
200
+ }
201
+ ```
202
+
203
+ ### Management API
204
+
205
+ | Method | Endpoint | Description |
206
+ |--------|----------|-------------|
207
+ | GET | `/health` | Health check |
208
+ | GET | `/api/dashboard` | Full dashboard data |
209
+ | GET | `/api/costs?days=30` | Cost summary |
210
+ | GET | `/api/budget` | Budget config and status |
211
+ | PUT | `/api/budget` | Update budget limits |
212
+ | GET | `/api/rate-limit` | Rate limit status |
213
+ | DELETE | `/api/cache` | Clear response cache |
214
+ | GET | `/dashboard` | Web dashboard UI |
215
+
216
+ ## Architecture
217
+
218
+ ```
219
+ src/
220
+ server.ts # Express HTTP server with all routes
221
+ proxy.ts # Main request handler (orchestrates everything)
222
+ router.ts # Smart model routing engine
223
+ task-classifier.ts # Task type detection from prompts
224
+ providers.ts # Provider adapters (OpenAI, Anthropic, Google, DeepSeek)
225
+ database.ts # SQLite database layer
226
+ rate-limiter.ts # Token bucket rate limiter
227
+ config.ts # Configuration, model catalog, logging
228
+ types.ts # TypeScript type definitions
229
+ utils/
230
+ encryption.ts # AES-256-GCM encryption for API keys
231
+ ```
232
+
233
+ ### Routing Algorithm
234
+
235
+ BudgetLLM scores each model using weighted criteria that vary by task type:
236
+
237
+ | Task Type | Quality Weight | Cost Weight | Speed Weight |
238
+ |-----------|---------------|-------------|--------------|
239
+ | Code | 40% | 45% | 15% |
240
+ | Creative | 70% | 15% | 15% |
241
+ | Reasoning | 65% | 25% | 10% |
242
+ | Chat | 20% | 60% | 20% |
243
+ | Summarization | 20% | 60% | 20% |
244
+ | Translation | 20% | 60% | 20% |
245
+ | Analysis | 50% | 35% | 15% |
246
+
247
+ Models also get a +20 score bonus if they declare the task type as a capability, and a -10 penalty otherwise.
248
+
249
+ ### Fallback Chain
250
+
251
+ When the primary provider fails, BudgetLLM tries up to 3 alternative providers in priority order. This means your app stays up even if one provider has an outage.
252
+
253
+ ### Caching
254
+
255
+ Identical prompts (same messages, model, temperature, max_tokens) are cached with a configurable TTL. Cached responses are served instantly with zero cost. The cache uses content-hash matching so even rephrased requests that happen to match the exact same parameters get the benefit.
256
+
257
+ ## Security
258
+
259
+ - **API Key Encryption** - Stored keys are encrypted with AES-256-GCM
260
+ - **Rate Limiting** - Token bucket algorithm prevents abuse
261
+ - **Input Validation** - Zod schema validation on all inputs
262
+ - **No Prompt Logging** - Request metadata is logged, but prompt content is never stored in logs
263
+ - **Helmet** - HTTP security headers via helmet middleware
264
+ - **Budget Enforcement** - Hard limits prevent runaway spending
265
+
266
+ ## Development
267
+
268
+ ```bash
269
+ # Install dependencies
270
+ npm install
271
+
272
+ # Run in development mode with hot reload
273
+ npm run dev
274
+
275
+ # Run tests
276
+ npm test
277
+
278
+ # Run tests in watch mode
279
+ npm run test:watch
280
+
281
+ # Generate coverage report
282
+ npm run test:coverage
283
+
284
+ # Type check
285
+ npm run lint
286
+
287
+ # Build for production
288
+ npm run build
289
+ ```
290
+
291
+ ## License
292
+
293
+ [MIT](LICENSE)
@@ -0,0 +1,77 @@
1
+ import { z } from 'zod';
2
+ import type { ProviderConfig, ModelInfo, BudgetConfig } from './types';
3
+ declare const envSchema: z.ZodObject<{
4
+ PORT: z.ZodDefault<z.ZodNumber>;
5
+ NODE_ENV: z.ZodDefault<z.ZodEnum<["development", "production", "test"]>>;
6
+ ENCRYPTION_KEY: z.ZodDefault<z.ZodString>;
7
+ DEFAULT_DAILY_BUDGET: z.ZodDefault<z.ZodNumber>;
8
+ DEFAULT_MONTHLY_BUDGET: z.ZodDefault<z.ZodNumber>;
9
+ DEFAULT_PER_REQUEST_CAP: z.ZodDefault<z.ZodNumber>;
10
+ CACHE_TTL: z.ZodDefault<z.ZodNumber>;
11
+ RATE_LIMIT_WINDOW_MS: z.ZodDefault<z.ZodNumber>;
12
+ RATE_LIMIT_MAX_REQUESTS: z.ZodDefault<z.ZodNumber>;
13
+ LOG_LEVEL: z.ZodDefault<z.ZodEnum<["debug", "info", "warn", "error"]>>;
14
+ OPENAI_API_KEY: z.ZodOptional<z.ZodString>;
15
+ ANTHROPIC_API_KEY: z.ZodOptional<z.ZodString>;
16
+ GOOGLE_API_KEY: z.ZodOptional<z.ZodString>;
17
+ DEEPSEEK_API_KEY: z.ZodOptional<z.ZodString>;
18
+ }, "strip", z.ZodTypeAny, {
19
+ PORT: number;
20
+ NODE_ENV: "development" | "production" | "test";
21
+ ENCRYPTION_KEY: string;
22
+ DEFAULT_DAILY_BUDGET: number;
23
+ DEFAULT_MONTHLY_BUDGET: number;
24
+ DEFAULT_PER_REQUEST_CAP: number;
25
+ CACHE_TTL: number;
26
+ RATE_LIMIT_WINDOW_MS: number;
27
+ RATE_LIMIT_MAX_REQUESTS: number;
28
+ LOG_LEVEL: "debug" | "info" | "warn" | "error";
29
+ OPENAI_API_KEY?: string | undefined;
30
+ ANTHROPIC_API_KEY?: string | undefined;
31
+ GOOGLE_API_KEY?: string | undefined;
32
+ DEEPSEEK_API_KEY?: string | undefined;
33
+ }, {
34
+ PORT?: number | undefined;
35
+ NODE_ENV?: "development" | "production" | "test" | undefined;
36
+ ENCRYPTION_KEY?: string | undefined;
37
+ DEFAULT_DAILY_BUDGET?: number | undefined;
38
+ DEFAULT_MONTHLY_BUDGET?: number | undefined;
39
+ DEFAULT_PER_REQUEST_CAP?: number | undefined;
40
+ CACHE_TTL?: number | undefined;
41
+ RATE_LIMIT_WINDOW_MS?: number | undefined;
42
+ RATE_LIMIT_MAX_REQUESTS?: number | undefined;
43
+ LOG_LEVEL?: "debug" | "info" | "warn" | "error" | undefined;
44
+ OPENAI_API_KEY?: string | undefined;
45
+ ANTHROPIC_API_KEY?: string | undefined;
46
+ GOOGLE_API_KEY?: string | undefined;
47
+ DEEPSEEK_API_KEY?: string | undefined;
48
+ }>;
49
+ export type EnvConfig = z.infer<typeof envSchema>;
50
+ export declare const env: {
51
+ PORT: number;
52
+ NODE_ENV: "development" | "production" | "test";
53
+ ENCRYPTION_KEY: string;
54
+ DEFAULT_DAILY_BUDGET: number;
55
+ DEFAULT_MONTHLY_BUDGET: number;
56
+ DEFAULT_PER_REQUEST_CAP: number;
57
+ CACHE_TTL: number;
58
+ RATE_LIMIT_WINDOW_MS: number;
59
+ RATE_LIMIT_MAX_REQUESTS: number;
60
+ LOG_LEVEL: "debug" | "info" | "warn" | "error";
61
+ OPENAI_API_KEY?: string | undefined;
62
+ ANTHROPIC_API_KEY?: string | undefined;
63
+ GOOGLE_API_KEY?: string | undefined;
64
+ DEEPSEEK_API_KEY?: string | undefined;
65
+ };
66
+ export declare function getProviders(): ProviderConfig[];
67
+ export declare const MODEL_CATALOG: ModelInfo[];
68
+ export declare function getDefaultBudget(): BudgetConfig;
69
+ export declare function getDbPath(): string;
70
+ export declare const log: {
71
+ debug: (msg: string, data?: unknown) => void;
72
+ info: (msg: string, data?: unknown) => void;
73
+ warn: (msg: string, data?: unknown) => void;
74
+ error: (msg: string, data?: unknown) => void;
75
+ };
76
+ export {};
77
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAMvE,QAAA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAeb,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAalD,eAAO,MAAM,GAAG;;;;;;;;;;;;;;;CAAa,CAAC;AAI9B,wBAAgB,YAAY,IAAI,cAAc,EAAE,CAuC/C;AAID,eAAO,MAAM,aAAa,EAAE,SAAS,EA0HpC,CAAC;AAIF,wBAAgB,gBAAgB,IAAI,YAAY,CAM/C;AAID,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAgBD,eAAO,MAAM,GAAG;iBACD,MAAM,SAAS,OAAO;gBAGvB,MAAM,SAAS,OAAO;gBAGtB,MAAM,SAAS,OAAO;iBAGrB,MAAM,SAAS,OAAO;CAGpC,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1,246 @@
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.log = exports.MODEL_CATALOG = exports.env = void 0;
7
+ exports.getProviders = getProviders;
8
+ exports.getDefaultBudget = getDefaultBudget;
9
+ exports.getDbPath = getDbPath;
10
+ const zod_1 = require("zod");
11
+ const dotenv_1 = __importDefault(require("dotenv"));
12
+ const path_1 = __importDefault(require("path"));
13
+ dotenv_1.default.config({ path: path_1.default.resolve(process.cwd(), '.env') });
14
+ // ---- Environment Schema ----
15
+ const envSchema = zod_1.z.object({
16
+ PORT: zod_1.z.coerce.number().default(3210),
17
+ NODE_ENV: zod_1.z.enum(['development', 'production', 'test']).default('development'),
18
+ ENCRYPTION_KEY: zod_1.z.string().min(32).default('dev_key_change_in_production_32chars!!'),
19
+ DEFAULT_DAILY_BUDGET: zod_1.z.coerce.number().positive().default(10),
20
+ DEFAULT_MONTHLY_BUDGET: zod_1.z.coerce.number().positive().default(200),
21
+ DEFAULT_PER_REQUEST_CAP: zod_1.z.coerce.number().positive().default(1),
22
+ CACHE_TTL: zod_1.z.coerce.number().positive().default(3600),
23
+ RATE_LIMIT_WINDOW_MS: zod_1.z.coerce.number().positive().default(60000),
24
+ RATE_LIMIT_MAX_REQUESTS: zod_1.z.coerce.number().positive().default(60),
25
+ LOG_LEVEL: zod_1.z.enum(['debug', 'info', 'warn', 'error']).default('info'),
26
+ OPENAI_API_KEY: zod_1.z.string().optional(),
27
+ ANTHROPIC_API_KEY: zod_1.z.string().optional(),
28
+ GOOGLE_API_KEY: zod_1.z.string().optional(),
29
+ DEEPSEEK_API_KEY: zod_1.z.string().optional(),
30
+ });
31
+ function parseEnv() {
32
+ const result = envSchema.safeParse(process.env);
33
+ if (!result.success) {
34
+ const errors = result.error.issues
35
+ .map((i) => `${i.path.join('.')}: ${i.message}`)
36
+ .join('; ');
37
+ throw new Error(`Configuration error: ${errors}`);
38
+ }
39
+ return result.data;
40
+ }
41
+ exports.env = parseEnv();
42
+ // ---- Provider Configurations ----
43
+ function getProviders() {
44
+ return [
45
+ {
46
+ id: 'openai',
47
+ name: 'OpenAI',
48
+ baseUrl: 'https://api.openai.com/v1',
49
+ apiKey: exports.env.OPENAI_API_KEY ?? '',
50
+ enabled: !!exports.env.OPENAI_API_KEY,
51
+ priority: 1,
52
+ timeoutMs: 30000,
53
+ },
54
+ {
55
+ id: 'anthropic',
56
+ name: 'Anthropic',
57
+ baseUrl: 'https://api.anthropic.com/v1',
58
+ apiKey: exports.env.ANTHROPIC_API_KEY ?? '',
59
+ enabled: !!exports.env.ANTHROPIC_API_KEY,
60
+ priority: 2,
61
+ timeoutMs: 30000,
62
+ },
63
+ {
64
+ id: 'google',
65
+ name: 'Google',
66
+ baseUrl: 'https://generativelanguage.googleapis.com/v1beta',
67
+ apiKey: exports.env.GOOGLE_API_KEY ?? '',
68
+ enabled: !!exports.env.GOOGLE_API_KEY,
69
+ priority: 3,
70
+ timeoutMs: 30000,
71
+ },
72
+ {
73
+ id: 'deepseek',
74
+ name: 'DeepSeek',
75
+ baseUrl: 'https://api.deepseek.com/v1',
76
+ apiKey: exports.env.DEEPSEEK_API_KEY ?? '',
77
+ enabled: !!exports.env.DEEPSEEK_API_KEY,
78
+ priority: 4,
79
+ timeoutMs: 30000,
80
+ },
81
+ ];
82
+ }
83
+ // ---- Model Catalog ----
84
+ exports.MODEL_CATALOG = [
85
+ // OpenAI
86
+ {
87
+ id: 'gpt-4o',
88
+ provider: 'openai',
89
+ displayName: 'GPT-4o',
90
+ inputPricePer1M: 2.50,
91
+ outputPricePer1M: 10.00,
92
+ contextWindow: 128000,
93
+ maxOutputTokens: 16384,
94
+ capabilities: ['code', 'creative', 'reasoning', 'chat', 'analysis', 'math'],
95
+ qualityScore: 9,
96
+ speedScore: 7,
97
+ costScore: 5,
98
+ },
99
+ {
100
+ id: 'gpt-4o-mini',
101
+ provider: 'openai',
102
+ displayName: 'GPT-4o Mini',
103
+ inputPricePer1M: 0.15,
104
+ outputPricePer1M: 0.60,
105
+ contextWindow: 128000,
106
+ maxOutputTokens: 16384,
107
+ capabilities: ['code', 'chat', 'summarization', 'translation'],
108
+ qualityScore: 7,
109
+ speedScore: 9,
110
+ costScore: 9,
111
+ },
112
+ {
113
+ id: 'gpt-4-turbo',
114
+ provider: 'openai',
115
+ displayName: 'GPT-4 Turbo',
116
+ inputPricePer1M: 10.00,
117
+ outputPricePer1M: 30.00,
118
+ contextWindow: 128000,
119
+ maxOutputTokens: 4096,
120
+ capabilities: ['code', 'creative', 'reasoning', 'chat', 'analysis', 'math'],
121
+ qualityScore: 9,
122
+ speedScore: 6,
123
+ costScore: 3,
124
+ },
125
+ // Anthropic
126
+ {
127
+ id: 'claude-sonnet-4-20250514',
128
+ provider: 'anthropic',
129
+ displayName: 'Claude Sonnet 4',
130
+ inputPricePer1M: 3.00,
131
+ outputPricePer1M: 15.00,
132
+ contextWindow: 200000,
133
+ maxOutputTokens: 8192,
134
+ capabilities: ['code', 'creative', 'reasoning', 'chat', 'analysis'],
135
+ qualityScore: 9,
136
+ speedScore: 7,
137
+ costScore: 5,
138
+ },
139
+ {
140
+ id: 'claude-haiku-3-5-20241022',
141
+ provider: 'anthropic',
142
+ displayName: 'Claude 3.5 Haiku',
143
+ inputPricePer1M: 0.80,
144
+ outputPricePer1M: 4.00,
145
+ contextWindow: 200000,
146
+ maxOutputTokens: 8192,
147
+ capabilities: ['code', 'chat', 'summarization', 'translation'],
148
+ qualityScore: 7,
149
+ speedScore: 9,
150
+ costScore: 7,
151
+ },
152
+ // Google
153
+ {
154
+ id: 'gemini-2.0-flash',
155
+ provider: 'google',
156
+ displayName: 'Gemini 2.0 Flash',
157
+ inputPricePer1M: 0.10,
158
+ outputPricePer1M: 0.40,
159
+ contextWindow: 1048576,
160
+ maxOutputTokens: 8192,
161
+ capabilities: ['code', 'chat', 'summarization', 'translation', 'analysis', 'math'],
162
+ qualityScore: 7,
163
+ speedScore: 10,
164
+ costScore: 10,
165
+ },
166
+ {
167
+ id: 'gemini-2.0-flash-lite',
168
+ provider: 'google',
169
+ displayName: 'Gemini 2.0 Flash Lite',
170
+ inputPricePer1M: 0.075,
171
+ outputPricePer1M: 0.30,
172
+ contextWindow: 1048576,
173
+ maxOutputTokens: 8192,
174
+ capabilities: ['chat', 'summarization', 'translation'],
175
+ qualityScore: 6,
176
+ speedScore: 10,
177
+ costScore: 10,
178
+ },
179
+ // DeepSeek
180
+ {
181
+ id: 'deepseek-chat',
182
+ provider: 'deepseek',
183
+ displayName: 'DeepSeek V3',
184
+ inputPricePer1M: 0.27,
185
+ outputPricePer1M: 1.10,
186
+ contextWindow: 65536,
187
+ maxOutputTokens: 8192,
188
+ capabilities: ['code', 'chat', 'reasoning', 'analysis', 'math'],
189
+ qualityScore: 8,
190
+ speedScore: 8,
191
+ costScore: 8,
192
+ },
193
+ {
194
+ id: 'deepseek-reasoner',
195
+ provider: 'deepseek',
196
+ displayName: 'DeepSeek R1',
197
+ inputPricePer1M: 0.55,
198
+ outputPricePer1M: 2.19,
199
+ contextWindow: 65536,
200
+ maxOutputTokens: 8192,
201
+ capabilities: ['reasoning', 'math', 'code', 'analysis'],
202
+ qualityScore: 9,
203
+ speedScore: 5,
204
+ costScore: 7,
205
+ },
206
+ ];
207
+ // ---- Budget Defaults ----
208
+ function getDefaultBudget() {
209
+ return {
210
+ dailyBudget: exports.env.DEFAULT_DAILY_BUDGET,
211
+ monthlyBudget: exports.env.DEFAULT_MONTHLY_BUDGET,
212
+ perRequestCap: exports.env.DEFAULT_PER_REQUEST_CAP,
213
+ };
214
+ }
215
+ // ---- Database Path ----
216
+ function getDbPath() {
217
+ return path_1.default.resolve(process.cwd(), 'data', 'budgetllm.db');
218
+ }
219
+ const LOG_LEVELS = {
220
+ debug: 0,
221
+ info: 1,
222
+ warn: 2,
223
+ error: 3,
224
+ };
225
+ function shouldLog(level) {
226
+ return LOG_LEVELS[level] >= LOG_LEVELS[exports.env.LOG_LEVEL];
227
+ }
228
+ exports.log = {
229
+ debug: (msg, data) => {
230
+ if (shouldLog('debug'))
231
+ console.debug(`[DEBUG] ${msg}`, data ?? '');
232
+ },
233
+ info: (msg, data) => {
234
+ if (shouldLog('info'))
235
+ console.info(`[INFO] ${msg}`, data ?? '');
236
+ },
237
+ warn: (msg, data) => {
238
+ if (shouldLog('warn'))
239
+ console.warn(`[WARN] ${msg}`, data ?? '');
240
+ },
241
+ error: (msg, data) => {
242
+ if (shouldLog('error'))
243
+ console.error(`[ERROR] ${msg}`, data ?? '');
244
+ },
245
+ };
246
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}