tokenfirewall 1.0.1
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/LICENSE +21 -0
- package/README.md +312 -0
- package/dist/adapters/anthropic.d.ts +5 -0
- package/dist/adapters/anthropic.js +34 -0
- package/dist/adapters/gemini.d.ts +5 -0
- package/dist/adapters/gemini.js +42 -0
- package/dist/adapters/grok.d.ts +6 -0
- package/dist/adapters/grok.js +33 -0
- package/dist/adapters/index.d.ts +5 -0
- package/dist/adapters/index.js +18 -0
- package/dist/adapters/kimi.d.ts +6 -0
- package/dist/adapters/kimi.js +33 -0
- package/dist/adapters/openai.d.ts +5 -0
- package/dist/adapters/openai.js +46 -0
- package/dist/core/budgetManager.d.ts +40 -0
- package/dist/core/budgetManager.js +116 -0
- package/dist/core/costEngine.d.ts +6 -0
- package/dist/core/costEngine.js +30 -0
- package/dist/core/pricingRegistry.d.ts +27 -0
- package/dist/core/pricingRegistry.js +75 -0
- package/dist/core/types.d.ts +66 -0
- package/dist/core/types.js +2 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.js +134 -0
- package/dist/interceptors/fetchInterceptor.d.ts +13 -0
- package/dist/interceptors/fetchInterceptor.js +73 -0
- package/dist/interceptors/sdkInterceptor.d.ts +9 -0
- package/dist/interceptors/sdkInterceptor.js +37 -0
- package/dist/introspection/contextRegistry.d.ts +30 -0
- package/dist/introspection/contextRegistry.js +81 -0
- package/dist/introspection/modelLister.d.ts +22 -0
- package/dist/introspection/modelLister.js +190 -0
- package/dist/logger.d.ts +11 -0
- package/dist/logger.js +31 -0
- package/dist/registry.d.ts +18 -0
- package/dist/registry.js +38 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tokenfirewall
|
|
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,312 @@
|
|
|
1
|
+
# tokenfirewall
|
|
2
|
+
|
|
3
|
+
Production-grade LLM cost enforcement middleware for Node.js with automatic tracking, budget management, and model discovery.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Multi-provider support**: OpenAI, Anthropic, Gemini, Grok, Kimi
|
|
8
|
+
- **Budget enforcement**: Warn or block when limits exceeded
|
|
9
|
+
- **Automatic cost tracking**: Real-time usage monitoring
|
|
10
|
+
- **Model discovery**: List available models with context limits
|
|
11
|
+
- **Context intelligence**: Budget-aware model selection
|
|
12
|
+
- **Extensible**: Add custom providers easily
|
|
13
|
+
- **Type-safe**: Full TypeScript support
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install tokenfirewall
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
const { createBudgetGuard, patchGlobalFetch } = require("tokenfirewall");
|
|
25
|
+
|
|
26
|
+
// Set budget limit
|
|
27
|
+
createBudgetGuard({
|
|
28
|
+
monthlyLimit: 100, // $100 USD
|
|
29
|
+
mode: "block" // or "warn"
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Enable tracking
|
|
33
|
+
patchGlobalFetch();
|
|
34
|
+
|
|
35
|
+
// Use any LLM API - tokenfirewall tracks everything
|
|
36
|
+
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: {
|
|
39
|
+
"Authorization": `Bearer ${process.env.OPENAI_API_KEY}`,
|
|
40
|
+
"Content-Type": "application/json"
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify({
|
|
43
|
+
model: "gpt-4o",
|
|
44
|
+
messages: [{ role: "user", content: "Hello!" }]
|
|
45
|
+
})
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## API Reference
|
|
50
|
+
|
|
51
|
+
### Budget Management
|
|
52
|
+
|
|
53
|
+
#### `createBudgetGuard(options)`
|
|
54
|
+
|
|
55
|
+
Initialize budget protection.
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
createBudgetGuard({
|
|
59
|
+
monthlyLimit: 100, // Required: monthly budget in USD
|
|
60
|
+
mode: "block" // Optional: "block" (default) or "warn"
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
#### `getBudgetStatus()`
|
|
65
|
+
|
|
66
|
+
Get current budget information.
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
const status = getBudgetStatus();
|
|
70
|
+
// {
|
|
71
|
+
// totalSpent: 45.23,
|
|
72
|
+
// limit: 100,
|
|
73
|
+
// remaining: 54.77,
|
|
74
|
+
// percentageUsed: 45.23
|
|
75
|
+
// }
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### `resetBudget()`
|
|
79
|
+
|
|
80
|
+
Reset budget tracking (useful for monthly resets).
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
resetBudget();
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Interception
|
|
87
|
+
|
|
88
|
+
#### `patchGlobalFetch()`
|
|
89
|
+
|
|
90
|
+
Intercept all fetch calls to track LLM usage.
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
patchGlobalFetch();
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### `patchProvider(providerName)`
|
|
97
|
+
|
|
98
|
+
Patch specific provider SDK (most use fetch internally).
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
patchProvider("openai");
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Model Discovery
|
|
105
|
+
|
|
106
|
+
#### `listAvailableModels(options)`
|
|
107
|
+
|
|
108
|
+
Discover available models with context limits and budget usage.
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
const models = await listAvailableModels({
|
|
112
|
+
provider: "openai",
|
|
113
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
114
|
+
includeBudgetUsage: true // Optional
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Returns:
|
|
118
|
+
// [
|
|
119
|
+
// {
|
|
120
|
+
// model: "gpt-4o",
|
|
121
|
+
// contextLimit: 128000,
|
|
122
|
+
// budgetUsagePercentage: 32.4
|
|
123
|
+
// }
|
|
124
|
+
// ]
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Extensibility
|
|
128
|
+
|
|
129
|
+
#### `registerAdapter(adapter)`
|
|
130
|
+
|
|
131
|
+
Add custom LLM provider.
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
registerAdapter({
|
|
135
|
+
name: "custom",
|
|
136
|
+
detect: (response) => /* detection logic */,
|
|
137
|
+
normalize: (response) => /* normalization logic */
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### `registerPricing(provider, model, pricing)`
|
|
142
|
+
|
|
143
|
+
Add custom pricing (per 1M tokens).
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
registerPricing("custom", "model-name", {
|
|
147
|
+
input: 0.001,
|
|
148
|
+
output: 0.002
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
#### `registerContextLimit(provider, model, contextLimit)`
|
|
153
|
+
|
|
154
|
+
Add custom context limit.
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
registerContextLimit("custom", "model-name", 131072);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Supported Providers
|
|
161
|
+
|
|
162
|
+
| Provider | Models | Context Limits |
|
|
163
|
+
|----------|--------|----------------|
|
|
164
|
+
| OpenAI | gpt-4o, gpt-4o-mini, gpt-3.5-turbo | 16K - 128K |
|
|
165
|
+
| Anthropic | claude-3-5-sonnet, claude-3-5-haiku | 200K |
|
|
166
|
+
| Gemini | gemini-2.5-pro, gemini-2.5-flash | 1M - 2M |
|
|
167
|
+
| Grok | grok-beta, llama-3.3-70b | 131K |
|
|
168
|
+
| Kimi | moonshot-v1-8k/32k/128k | 8K - 128K |
|
|
169
|
+
|
|
170
|
+
## Usage Examples
|
|
171
|
+
|
|
172
|
+
### Basic Usage
|
|
173
|
+
|
|
174
|
+
```javascript
|
|
175
|
+
const { createBudgetGuard, patchGlobalFetch } = require("tokenfirewall");
|
|
176
|
+
|
|
177
|
+
createBudgetGuard({ monthlyLimit: 100, mode: "block" });
|
|
178
|
+
patchGlobalFetch();
|
|
179
|
+
|
|
180
|
+
// Make LLM calls as usual - automatically tracked
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Budget-Aware Model Selection
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
const { listAvailableModels, getBudgetStatus } = require("tokenfirewall");
|
|
187
|
+
|
|
188
|
+
const models = await listAvailableModels({
|
|
189
|
+
provider: "openai",
|
|
190
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
191
|
+
includeBudgetUsage: true
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const status = getBudgetStatus();
|
|
195
|
+
if (status.remaining < 10) {
|
|
196
|
+
console.log("Low budget - use cheaper models");
|
|
197
|
+
const cheapModels = models.filter(m => m.model.includes("mini"));
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Context-Aware Routing
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
const { listAvailableModels } = require("tokenfirewall");
|
|
205
|
+
|
|
206
|
+
const models = await listAvailableModels({
|
|
207
|
+
provider: "openai",
|
|
208
|
+
apiKey: process.env.OPENAI_API_KEY
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Find model with sufficient context
|
|
212
|
+
const suitable = models.find(m =>
|
|
213
|
+
m.contextLimit && m.contextLimit >= promptTokens * 1.5
|
|
214
|
+
);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Custom Provider
|
|
218
|
+
|
|
219
|
+
```javascript
|
|
220
|
+
const { registerAdapter, registerPricing } = require("tokenfirewall");
|
|
221
|
+
|
|
222
|
+
// Add Ollama support
|
|
223
|
+
registerAdapter({
|
|
224
|
+
name: "ollama",
|
|
225
|
+
detect: (response) => response?.model && response?.prompt_eval_count !== undefined,
|
|
226
|
+
normalize: (response) => ({
|
|
227
|
+
provider: "ollama",
|
|
228
|
+
model: response.model,
|
|
229
|
+
inputTokens: response.prompt_eval_count || 0,
|
|
230
|
+
outputTokens: response.eval_count || 0,
|
|
231
|
+
totalTokens: (response.prompt_eval_count || 0) + (response.eval_count || 0)
|
|
232
|
+
})
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
registerPricing("ollama", "llama3.2", { input: 0, output: 0 });
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## TypeScript Support
|
|
239
|
+
|
|
240
|
+
Full type definitions included:
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
import {
|
|
244
|
+
createBudgetGuard,
|
|
245
|
+
listAvailableModels,
|
|
246
|
+
BudgetGuardOptions,
|
|
247
|
+
ModelInfo,
|
|
248
|
+
ListModelsOptions
|
|
249
|
+
} from "tokenfirewall";
|
|
250
|
+
|
|
251
|
+
const options: BudgetGuardOptions = {
|
|
252
|
+
monthlyLimit: 100,
|
|
253
|
+
mode: "block"
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const models: ModelInfo[] = await listAvailableModels({
|
|
257
|
+
provider: "openai",
|
|
258
|
+
apiKey: process.env.OPENAI_API_KEY!
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Architecture
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
tokenfirewall/
|
|
266
|
+
├── core/ # Provider-agnostic logic
|
|
267
|
+
├── adapters/ # Provider-specific normalization
|
|
268
|
+
├── interceptors/ # Request/response capture
|
|
269
|
+
├── introspection/ # Model discovery
|
|
270
|
+
└── registry/ # Adapter management
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Adding a new provider requires only creating an adapter file - no core changes needed.
|
|
274
|
+
|
|
275
|
+
## Examples
|
|
276
|
+
|
|
277
|
+
See the `examples/` directory for complete working examples:
|
|
278
|
+
|
|
279
|
+
- `basic-usage.js` - Simple OpenAI example
|
|
280
|
+
- `multiple-providers.js` - Track multiple providers
|
|
281
|
+
- `with-sdk.js` - Use with official SDKs
|
|
282
|
+
- `model-discovery.js` - Model discovery
|
|
283
|
+
- `context-aware-routing.js` - Intelligent routing
|
|
284
|
+
- `custom-provider.js` - Add custom provider
|
|
285
|
+
- `gemini-complete-demo.js` - Complete Gemini demo
|
|
286
|
+
|
|
287
|
+
## Best Practices
|
|
288
|
+
|
|
289
|
+
1. **Set realistic budgets**: Start with a conservative limit
|
|
290
|
+
2. **Use warn mode in development**: Switch to block in production
|
|
291
|
+
3. **Reset monthly**: Automate budget resets with cron
|
|
292
|
+
4. **Cache model lists**: Model availability doesn't change often
|
|
293
|
+
5. **Monitor logs**: Review structured JSON output regularly
|
|
294
|
+
|
|
295
|
+
## Limitations
|
|
296
|
+
|
|
297
|
+
- In-memory tracking only (no persistence in V1)
|
|
298
|
+
- No streaming support yet
|
|
299
|
+
- Context limits are static (not from provider APIs)
|
|
300
|
+
- Budget tracking is local only (not provider-side billing)
|
|
301
|
+
|
|
302
|
+
## License
|
|
303
|
+
|
|
304
|
+
MIT
|
|
305
|
+
|
|
306
|
+
## Contributing
|
|
307
|
+
|
|
308
|
+
Contributions welcome! Please open an issue or PR.
|
|
309
|
+
|
|
310
|
+
## Support
|
|
311
|
+
|
|
312
|
+
For issues and questions, please open a GitHub issue.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.anthropicAdapter = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Anthropic adapter - normalizes Anthropic API responses
|
|
6
|
+
*/
|
|
7
|
+
exports.anthropicAdapter = {
|
|
8
|
+
name: "anthropic",
|
|
9
|
+
detect(response) {
|
|
10
|
+
if (!response || typeof response !== "object") {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const resp = response;
|
|
14
|
+
return (typeof resp.id === "string" &&
|
|
15
|
+
resp.type === "message" &&
|
|
16
|
+
typeof resp.model === "string" &&
|
|
17
|
+
resp.usage !== undefined);
|
|
18
|
+
},
|
|
19
|
+
normalize(response) {
|
|
20
|
+
const resp = response;
|
|
21
|
+
if (!resp.usage || !resp.model) {
|
|
22
|
+
throw new Error("TokenFirewall: Invalid Anthropic response format");
|
|
23
|
+
}
|
|
24
|
+
const inputTokens = resp.usage.input_tokens ?? 0;
|
|
25
|
+
const outputTokens = resp.usage.output_tokens ?? 0;
|
|
26
|
+
return {
|
|
27
|
+
provider: "anthropic",
|
|
28
|
+
model: resp.model,
|
|
29
|
+
inputTokens,
|
|
30
|
+
outputTokens,
|
|
31
|
+
totalTokens: inputTokens + outputTokens,
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.geminiAdapter = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Gemini adapter - normalizes Google Gemini API responses
|
|
6
|
+
*/
|
|
7
|
+
exports.geminiAdapter = {
|
|
8
|
+
name: "gemini",
|
|
9
|
+
detect(response) {
|
|
10
|
+
if (!response || typeof response !== "object") {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const resp = response;
|
|
14
|
+
return (Array.isArray(resp.candidates) &&
|
|
15
|
+
resp.usageMetadata !== undefined &&
|
|
16
|
+
typeof resp.usageMetadata === "object");
|
|
17
|
+
},
|
|
18
|
+
normalize(response, request) {
|
|
19
|
+
const resp = response;
|
|
20
|
+
if (!resp.usageMetadata) {
|
|
21
|
+
throw new Error("TokenFirewall: Invalid Gemini response format");
|
|
22
|
+
}
|
|
23
|
+
// Extract model from request or use default
|
|
24
|
+
let model = "gemini-1.5-flash";
|
|
25
|
+
if (request && typeof request === "object") {
|
|
26
|
+
const req = request;
|
|
27
|
+
if (req.model) {
|
|
28
|
+
model = req.model;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (resp.modelVersion) {
|
|
32
|
+
model = resp.modelVersion;
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
provider: "gemini",
|
|
36
|
+
model,
|
|
37
|
+
inputTokens: resp.usageMetadata.promptTokenCount ?? 0,
|
|
38
|
+
outputTokens: resp.usageMetadata.candidatesTokenCount ?? 0,
|
|
39
|
+
totalTokens: resp.usageMetadata.totalTokenCount ?? 0,
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.grokAdapter = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Grok adapter - normalizes Grok API responses
|
|
6
|
+
* Grok uses OpenAI-compatible format and supports both Grok and Llama models
|
|
7
|
+
*/
|
|
8
|
+
exports.grokAdapter = {
|
|
9
|
+
name: "grok",
|
|
10
|
+
detect(response) {
|
|
11
|
+
if (!response || typeof response !== "object") {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
const resp = response;
|
|
15
|
+
return (typeof resp.id === "string" &&
|
|
16
|
+
typeof resp.model === "string" &&
|
|
17
|
+
(resp.model.startsWith("grok") || resp.model.includes("llama")) &&
|
|
18
|
+
resp.usage !== undefined);
|
|
19
|
+
},
|
|
20
|
+
normalize(response) {
|
|
21
|
+
const resp = response;
|
|
22
|
+
if (!resp.usage || !resp.model) {
|
|
23
|
+
throw new Error("TokenFirewall: Invalid Grok response format");
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
provider: "grok",
|
|
27
|
+
model: resp.model,
|
|
28
|
+
inputTokens: resp.usage.prompt_tokens ?? 0,
|
|
29
|
+
outputTokens: resp.usage.completion_tokens ?? 0,
|
|
30
|
+
totalTokens: resp.usage.total_tokens ?? 0,
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.adapters = void 0;
|
|
4
|
+
const openai_1 = require("./openai");
|
|
5
|
+
const anthropic_1 = require("./anthropic");
|
|
6
|
+
const gemini_1 = require("./gemini");
|
|
7
|
+
const grok_1 = require("./grok");
|
|
8
|
+
const kimi_1 = require("./kimi");
|
|
9
|
+
/**
|
|
10
|
+
* Registry of all provider adapters
|
|
11
|
+
*/
|
|
12
|
+
exports.adapters = [
|
|
13
|
+
openai_1.openaiAdapter,
|
|
14
|
+
anthropic_1.anthropicAdapter,
|
|
15
|
+
gemini_1.geminiAdapter,
|
|
16
|
+
grok_1.grokAdapter,
|
|
17
|
+
kimi_1.kimiAdapter,
|
|
18
|
+
];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.kimiAdapter = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Kimi adapter - normalizes Kimi (Moonshot AI) API responses
|
|
6
|
+
* Kimi uses OpenAI-compatible format
|
|
7
|
+
*/
|
|
8
|
+
exports.kimiAdapter = {
|
|
9
|
+
name: "kimi",
|
|
10
|
+
detect(response) {
|
|
11
|
+
if (!response || typeof response !== "object") {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
const resp = response;
|
|
15
|
+
return (typeof resp.id === "string" &&
|
|
16
|
+
typeof resp.model === "string" &&
|
|
17
|
+
resp.model.startsWith("moonshot") &&
|
|
18
|
+
resp.usage !== undefined);
|
|
19
|
+
},
|
|
20
|
+
normalize(response) {
|
|
21
|
+
const resp = response;
|
|
22
|
+
if (!resp.usage || !resp.model) {
|
|
23
|
+
throw new Error("TokenFirewall: Invalid Kimi response format");
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
provider: "kimi",
|
|
27
|
+
model: resp.model,
|
|
28
|
+
inputTokens: resp.usage.prompt_tokens ?? 0,
|
|
29
|
+
outputTokens: resp.usage.completion_tokens ?? 0,
|
|
30
|
+
totalTokens: resp.usage.total_tokens ?? 0,
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.openaiAdapter = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* OpenAI adapter - normalizes OpenAI API responses
|
|
6
|
+
*/
|
|
7
|
+
exports.openaiAdapter = {
|
|
8
|
+
name: "openai",
|
|
9
|
+
detect(response) {
|
|
10
|
+
if (!response || typeof response !== "object") {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const resp = response;
|
|
14
|
+
// Check basic OpenAI structure
|
|
15
|
+
const hasBasicStructure = (typeof resp.id === "string" &&
|
|
16
|
+
typeof resp.object === "string" &&
|
|
17
|
+
typeof resp.model === "string" &&
|
|
18
|
+
resp.usage !== undefined &&
|
|
19
|
+
typeof resp.usage === "object");
|
|
20
|
+
if (!hasBasicStructure) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
// Exclude Grok and Llama models (they have their own adapter)
|
|
24
|
+
if (resp.model && (resp.model.startsWith("grok") || resp.model.includes("llama"))) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
// Exclude Kimi models (they have their own adapter)
|
|
28
|
+
if (resp.model && resp.model.startsWith("moonshot")) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
},
|
|
33
|
+
normalize(response) {
|
|
34
|
+
const resp = response;
|
|
35
|
+
if (!resp.usage || !resp.model) {
|
|
36
|
+
throw new Error("TokenFirewall: Invalid OpenAI response format");
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
provider: "openai",
|
|
40
|
+
model: resp.model,
|
|
41
|
+
inputTokens: resp.usage.prompt_tokens ?? 0,
|
|
42
|
+
outputTokens: resp.usage.completion_tokens ?? 0,
|
|
43
|
+
totalTokens: resp.usage.total_tokens ?? 0,
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { BudgetGuardOptions, BudgetStatus } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Budget manager - encapsulates all budget tracking state
|
|
4
|
+
*/
|
|
5
|
+
export declare class BudgetManager {
|
|
6
|
+
private totalSpent;
|
|
7
|
+
private limit;
|
|
8
|
+
private mode;
|
|
9
|
+
private trackingLock;
|
|
10
|
+
constructor(options: BudgetGuardOptions);
|
|
11
|
+
/**
|
|
12
|
+
* Track a new cost and enforce budget limits
|
|
13
|
+
* Uses locking to prevent race conditions
|
|
14
|
+
* @throws Error if budget exceeded in block mode
|
|
15
|
+
*/
|
|
16
|
+
track(cost: number): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Get current budget status
|
|
19
|
+
*/
|
|
20
|
+
getStatus(): BudgetStatus;
|
|
21
|
+
/**
|
|
22
|
+
* Reset budget tracking (useful for monthly resets)
|
|
23
|
+
*/
|
|
24
|
+
reset(): void;
|
|
25
|
+
/**
|
|
26
|
+
* Export budget state for persistence
|
|
27
|
+
*/
|
|
28
|
+
exportState(): {
|
|
29
|
+
totalSpent: number;
|
|
30
|
+
limit: number;
|
|
31
|
+
mode: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Import budget state from persistence
|
|
35
|
+
* Validates state before importing
|
|
36
|
+
*/
|
|
37
|
+
importState(state: {
|
|
38
|
+
totalSpent: number;
|
|
39
|
+
}): void;
|
|
40
|
+
}
|