@salimassili/ai-costguard 2.0.0 → 2.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +59 -50
  2. package/LICENSE +21 -21
  3. package/README.md +399 -339
  4. package/benchmarks/run.mjs +229 -229
  5. package/benchmarks/token-accuracy.mjs +86 -0
  6. package/dist/core/CostGuard.d.ts +1 -1
  7. package/dist/core/CostGuard.d.ts.map +1 -1
  8. package/dist/core/CostGuard.js +1 -1
  9. package/dist/core/CostGuard.js.map +1 -1
  10. package/dist/core/GuardPro.d.ts +1 -13
  11. package/dist/core/GuardPro.d.ts.map +1 -1
  12. package/dist/core/GuardPro.js +7 -19
  13. package/dist/core/GuardPro.js.map +1 -1
  14. package/dist/core/types.d.ts +1 -3
  15. package/dist/core/types.d.ts.map +1 -1
  16. package/dist/dashboard.js +49 -49
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/pricing/index.d.ts +7 -0
  22. package/dist/pricing/index.d.ts.map +1 -1
  23. package/dist/pricing/index.js +7 -0
  24. package/dist/pricing/index.js.map +1 -1
  25. package/dist/pro.d.ts +1 -1
  26. package/dist/pro.d.ts.map +1 -1
  27. package/dist/pro.js +1 -1
  28. package/dist/pro.js.map +1 -1
  29. package/docs/BENCHMARKS.md +60 -41
  30. package/docs/DASHBOARD.md +61 -61
  31. package/docs/INTEGRATIONS.md +153 -153
  32. package/examples/integrations/anthropic-workflow-budget.mjs +36 -36
  33. package/examples/integrations/ci-budget-check.mjs +32 -32
  34. package/examples/integrations/crewai-budget-gate.mjs +31 -31
  35. package/examples/integrations/langchain-retry-storm.mjs +32 -32
  36. package/examples/integrations/mastra-agent.mjs +41 -41
  37. package/examples/integrations/openai-agent-loop.mjs +44 -44
  38. package/examples/integrations/vercel-ai-chatbot.mjs +29 -29
  39. package/package.json +71 -69
package/README.md CHANGED
@@ -1,380 +1,440 @@
1
- # AI CostGuard
2
-
3
- AI CostGuard is a local-first runtime safety layer for AI agents that prevents runaway costs, loops, retries, and budget explosions before API calls execute. It wraps OpenAI-compatible clients and function-style SDK calls, estimates request cost locally, blocks budget overruns, detects repeated prompts, emits structured events, and exposes CLI checks plus a local dashboard.
4
-
1
+ # AI CostGuard
2
+ [![npm version](https://img.shields.io/npm/v/@salimassili/ai-costguard)](https://www.npmjs.com/package/@salimassili/ai-costguard)
3
+
4
+ AI CostGuard is a local-first runtime safety layer for AI agents that prevents runaway costs, loops, retries, and budget explosions before API calls execute. It wraps OpenAI-compatible clients and function-style SDK calls, estimates request cost locally, blocks budget overruns, detects repeated prompts, emits structured events, and exposes CLI checks plus a local dashboard.
5
+
5
6
  It is local-first. It does not include a SaaS control plane, cloud dashboard, proxy gateway, telemetry service, billing reconciliation service, or hard security boundary.
6
7
 
7
- ## Install
8
+ ## What AI CostGuard Does
8
9
 
9
- ```bash
10
- npm install @salimassili/ai-costguard
11
- ```
10
+ - Checks selected AI SDK calls before they execute.
11
+ - Estimates request cost from model pricing, prompt text, and reserved output tokens.
12
+ - Blocks unknown models unless explicit pricing is supplied.
13
+ - Blocks budget overruns, repeated prompt loops, retry storms, and max-step overruns.
14
+ - Emits structured errors and local events your app can handle.
12
15
 
13
- ## Quick Start
16
+ ## What AI CostGuard Does Not Do
14
17
 
15
- ```ts
16
- import OpenAI from 'openai';
17
- import { guard, GuardError } from '@salimassili/ai-costguard';
18
+ - It does not call providers for real-time pricing.
19
+ - It does not reconcile provider invoices or replace provider billing alerts.
20
+ - It does not provide auth, API-key security, or a hard security boundary.
21
+ - It does not run a hosted dashboard, SaaS backend, or cloud telemetry service.
22
+ - It does not guarantee exact tokenizer parity with OpenAI, Anthropic, or other providers.
18
23
 
19
- const openai = guard(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }), {
20
- budget: 5,
21
- maxSteps: 50,
22
- scope: { projectId: 'my-app' },
23
- });
24
-
25
- try {
26
- const response = await openai.chat.completions.create({
27
- model: 'gpt-4o-mini',
28
- messages: [{ role: 'user', content: 'Write a short summary.' }],
29
- max_tokens: 200,
30
- });
31
-
32
- console.log(response.choices[0]?.message?.content);
33
- } catch (error) {
34
- if (error instanceof GuardError) {
35
- console.error(error.code, error.message, error.context);
36
- } else {
37
- throw error;
38
- }
24
+ ## Install
25
+
26
+ ```bash
27
+ npm install @salimassili/ai-costguard
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ```ts
33
+ import OpenAI from 'openai';
34
+ import { guard, GuardError } from '@salimassili/ai-costguard';
35
+
36
+ const openai = guard(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }), {
37
+ budget: 5,
38
+ maxSteps: 50,
39
+ scope: { projectId: 'my-app' },
40
+ });
41
+
42
+ try {
43
+ const response = await openai.chat.completions.create({
44
+ model: 'gpt-4o-mini',
45
+ messages: [{ role: 'user', content: 'Write a short summary.' }],
46
+ max_tokens: 200,
47
+ });
48
+
49
+ console.log(response.choices[0]?.message?.content);
50
+ } catch (error) {
51
+ if (error instanceof GuardError) {
52
+ console.error(error.code, error.message, error.context);
53
+ } else {
54
+ throw error;
55
+ }
39
56
  }
40
57
  ```
41
58
 
42
- ## What It Guards
43
-
44
- By default AI CostGuard evaluates these SDK method paths:
45
-
46
- - `chat.completions.create`
47
- - `completions.create`
48
- - `responses.create`
49
- - `messages.create`
59
+ ## Before / After
50
60
 
51
- Other client methods are passed through without cost checks. To protect a custom client method:
61
+ Without AI CostGuard:
52
62
 
53
63
  ```ts
54
- const client = guard(customClient, {
55
- budget: 2,
56
- guardedMethods: ['agent.run'],
57
- pricingOverrides: [
58
- {
59
- model: 'internal-model',
60
- inputPer1kTokens: 0.001,
61
- outputPer1kTokens: 0.002,
62
- lastUpdated: '2026-06-07',
63
- source: 'internal pricing sheet',
64
- },
65
- ],
66
- });
64
+ await openai.chat.completions.create(request);
67
65
  ```
68
66
 
69
- For function-style SDKs such as Vercel AI SDK adapters, LangChain wrappers, or agent runners:
67
+ With AI CostGuard:
70
68
 
71
69
  ```ts
72
- import { guardFunction } from '@salimassili/ai-costguard';
73
-
74
- const guardedGenerateText = guardFunction(generateTextAdapter, {
75
- budget: 1,
76
- scope: { projectId: 'chatbot' },
70
+ const openai = guard(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }), {
71
+ budget: 5,
72
+ maxSteps: 50,
73
+ scope: { projectId: 'agent-api', sessionId: runId },
77
74
  });
78
75
 
79
- await guardedGenerateText({
80
- model: 'gpt-4o-mini',
81
- prompt: 'Answer the user in one paragraph.',
82
- max_tokens: 200,
83
- });
76
+ await openai.chat.completions.create(request);
84
77
  ```
85
78
 
86
- ## Decisions And Errors
87
-
88
- Blocked requests throw `GuardError` before the provider method is called.
89
-
90
- ```ts
91
- try {
92
- await openai.chat.completions.create(request);
93
- } catch (error) {
94
- if (error instanceof GuardError) {
95
- console.log(error.code);
96
- console.log(error.metadata);
97
- }
98
- }
99
- ```
100
-
101
- Current runtime block codes:
102
-
103
- - `UNKNOWN_MODEL`
104
- - `BUDGET_EXCEEDED`
105
- - `MAX_STEPS_EXCEEDED`
106
- - `LOOP_DETECTED`
107
- - `RETRY_STORM_DETECTED`
108
-
109
- `INVALID_LICENSE` remains in the exported type for compatibility with older callers, but the current Pro helper does not enforce local license checks.
110
-
79
+ ## What It Guards
80
+
81
+ By default AI CostGuard evaluates these SDK method paths:
82
+
83
+ - `chat.completions.create`
84
+ - `completions.create`
85
+ - `responses.create`
86
+ - `messages.create`
87
+
88
+ Other client methods are passed through without cost checks. To protect a custom client method:
89
+
90
+ ```ts
91
+ const client = guard(customClient, {
92
+ budget: 2,
93
+ guardedMethods: ['agent.run'],
94
+ pricingOverrides: [
95
+ {
96
+ model: 'internal-model',
97
+ inputPer1kTokens: 0.001,
98
+ outputPer1kTokens: 0.002,
99
+ lastUpdated: '2026-06-07',
100
+ source: 'internal pricing sheet',
101
+ },
102
+ ],
103
+ });
104
+ ```
105
+
106
+ For function-style SDKs such as Vercel AI SDK adapters, LangChain wrappers, or agent runners:
107
+
108
+ ```ts
109
+ import { guardFunction } from '@salimassili/ai-costguard';
110
+
111
+ const guardedGenerateText = guardFunction(generateTextAdapter, {
112
+ budget: 1,
113
+ scope: { projectId: 'chatbot' },
114
+ });
115
+
116
+ await guardedGenerateText({
117
+ model: 'gpt-4o-mini',
118
+ prompt: 'Answer the user in one paragraph.',
119
+ max_tokens: 200,
120
+ });
121
+ ```
122
+
123
+ ## Decisions And Errors
124
+
125
+ Blocked requests throw `GuardError` before the provider method is called.
126
+
127
+ ```ts
128
+ try {
129
+ await openai.chat.completions.create(request);
130
+ } catch (error) {
131
+ if (error instanceof GuardError) {
132
+ console.log(error.code);
133
+ console.log(error.metadata);
134
+ }
135
+ }
136
+ ```
137
+
138
+ Current runtime block codes:
139
+
140
+ - `UNKNOWN_MODEL`
141
+ - `BUDGET_EXCEEDED`
142
+ - `MAX_STEPS_EXCEEDED`
143
+ - `LOOP_DETECTED`
144
+ - `RETRY_STORM_DETECTED`
145
+
111
146
  ## Configuration
112
-
113
- ```ts
114
- guard(client, {
115
- budget: 10,
116
- maxSteps: 100,
117
- behaviorAnalysis: true,
118
- maxHistory: 32,
119
- historyTtlMs: 5 * 60 * 1000,
120
- loopSimilarityThreshold: 0.85,
121
- loopMinRepeats: 2,
122
- retryThreshold: 2,
123
- scope: {
124
- projectId: 'production-api',
125
- userId: 'optional-user',
126
- sessionId: 'optional-agent-run',
127
- },
128
- guardedMethods: ['chat.completions.create', 'responses.create'],
129
- pricingOverrides: [],
130
- webhooks: {
131
- slack: process.env.SLACK_WEBHOOK,
132
- discord: process.env.DISCORD_WEBHOOK,
133
- retries: 2,
134
- timeoutMs: 1500,
135
- },
136
- eventLogPath: '.ai-costguard/events.jsonl',
137
- eventLogPrompt: 'none',
138
- });
139
- ```
140
-
147
+
148
+ ```ts
149
+ guard(client, {
150
+ budget: 10,
151
+ maxSteps: 100,
152
+ behaviorAnalysis: true,
153
+ maxHistory: 32,
154
+ historyTtlMs: 5 * 60 * 1000,
155
+ loopSimilarityThreshold: 0.85,
156
+ loopMinRepeats: 2,
157
+ retryThreshold: 2,
158
+ scope: {
159
+ projectId: 'production-api',
160
+ userId: 'optional-user',
161
+ sessionId: 'optional-agent-run',
162
+ },
163
+ guardedMethods: ['chat.completions.create', 'responses.create'],
164
+ pricingOverrides: [],
165
+ webhooks: {
166
+ slack: process.env.SLACK_WEBHOOK,
167
+ discord: process.env.DISCORD_WEBHOOK,
168
+ retries: 2,
169
+ timeoutMs: 1500,
170
+ },
171
+ eventLogPath: '.ai-costguard/events.jsonl',
172
+ eventLogPrompt: 'none',
173
+ });
174
+ ```
175
+
141
176
  `scope` isolates budgets and behavior history. If no scope is supplied, the guard uses one process-local default scope.
142
177
 
143
- ## Accounting Semantics
144
-
145
- AI CostGuard is a pre-call estimator, not a billing ledger.
146
-
147
- - `attemptedCost`: estimated cost of every guarded attempt, including blocked attempts.
148
- - `totalCost`: estimated cost of allowed calls.
149
- - `blockedCost`: estimated cost stopped before provider execution.
150
- - `actualCost`: provider-reported usage cost when the response includes recognizable `usage` fields.
151
-
152
- Budget decisions use estimated allowed cost. Actual usage is recorded for observability but does not rewrite earlier decisions.
153
-
154
- ## Pricing
155
-
156
- Known model pricing comes from built-in registry entries, runtime registrations, or per-guard overrides. Unknown models are blocked by default.
157
-
158
- ```ts
159
- import { registerPricing } from '@salimassili/ai-costguard';
160
-
161
- registerPricing([
162
- {
163
- model: 'my-company-model',
164
- inputPer1kTokens: 0.001,
165
- outputPer1kTokens: 0.002,
166
- lastUpdated: '2026-06-07',
167
- source: 'internal',
168
- },
169
- ]);
170
- ```
171
-
172
- If you intentionally want fallback pricing for unknown models:
173
-
174
- ```ts
175
- guard(client, {
176
- budget: 5,
177
- unknownModelPolicy: 'fallback',
178
- unknownModelPricing: {
179
- model: 'fallback',
180
- inputPer1kTokens: 0.001,
181
- outputPer1kTokens: 0.002,
182
- lastUpdated: '2026-06-07',
183
- source: 'application fallback',
184
- },
185
- });
186
- ```
187
-
188
- Pricing changes frequently. Verify provider pricing before production use and override entries when needed.
189
-
190
- ## Events
191
-
192
- ```ts
193
- const unsubscribe = openai.on('block', (event) => {
194
- console.log(event.code, event.reason, event.context.estimatedCost);
195
- });
196
-
197
- unsubscribe();
198
- ```
199
-
200
- Supported events are `cost`, `allow`, and `block`. Handler errors are swallowed so observability code cannot change guard decisions.
178
+ ## Loop Detection Tuning
201
179
 
202
- ## Local Dashboard
180
+ Default loop detection uses character trigram cosine similarity with `loopSimilarityThreshold: 0.85` and `loopMinRepeats: 2`.
203
181
 
204
- Opt into a local JSONL event log:
182
+ - Higher threshold, such as `0.95`: fewer false positives, but near-duplicate loops can slip through.
183
+ - Lower threshold, such as `0.75`: catches looser repeats, but unrelated prompts can be blocked.
184
+ - Higher `loopMinRepeats`: waits for more repeated prompts before blocking.
185
+ - Lower `loopMinRepeats`: blocks faster, but is more aggressive.
205
186
 
206
187
  ```ts
207
188
  const openai = guard(client, {
208
189
  budget: 5,
209
- eventLogPath: '.ai-costguard/events.jsonl',
210
- });
211
- ```
212
-
213
- Start the local-only dashboard:
214
-
215
- ```bash
216
- ai-costguard dashboard --events .ai-costguard/events.jsonl --budget 5
217
- ```
218
-
219
- For one-off package execution:
220
-
221
- ```bash
222
- npx @salimassili/ai-costguard dashboard --events .ai-costguard/events.jsonl --budget 5
223
- ```
224
-
225
- If the package is installed locally, `npx ai-costguard dashboard` also works. The dashboard binds to `127.0.0.1` by default and reads only local event files.
226
-
227
- For CI or terminal output:
228
-
229
- ```bash
230
- ai-costguard dashboard --events .ai-costguard/events.jsonl --budget 5 --once --json
231
- ```
232
-
233
- See `docs/DASHBOARD.md`.
234
-
235
- ## Integrations
236
-
237
- Runnable mocked examples are included for:
238
-
239
- - OpenAI SDK agent loop protection
240
- - Anthropic SDK workflow budget guard
241
- - Vercel AI SDK chatbot budget cap
242
- - LangChain retry-storm prevention
243
- - Mastra-style agent runner protection
244
- - CrewAI launch/budget gate
245
- - CI budget checks
246
-
247
- See `docs/INTEGRATIONS.md` and `examples/integrations`.
248
-
249
- ## Express Middleware
250
-
251
- The middleware attaches a manual checker. It does not automatically parse or inspect every route.
252
-
253
- ```ts
254
- import { middleware, GuardError } from '@salimassili/ai-costguard';
255
-
256
- app.use(middleware({ budget: 2 }));
257
-
258
- app.post('/chat', async (req, res, next) => {
259
- try {
260
- req.localSafety.check({
261
- model: 'gpt-4o-mini',
262
- tokens: 500,
263
- inputTokens: 100,
264
- outputTokens: 400,
265
- estimatedCost: 0.0003,
266
- timestamp: Date.now(),
267
- prompt: String(req.body?.prompt ?? ''),
268
- });
269
-
270
- res.json({ ok: true });
271
- } catch (error) {
272
- if (error instanceof GuardError) {
273
- res.status(403).json({ code: error.code, reason: error.message });
274
- return;
275
- }
276
- next(error);
277
- }
278
- });
279
- ```
280
-
281
- ## Optional Redis / Pro Helper
282
-
283
- Redis-backed shared spend tracking is isolated behind a subpath import:
284
-
285
- ```ts
286
- import { GuardPro } from '@salimassili/ai-costguard/pro';
287
-
288
- const pro = new GuardPro({
289
- redisUrl: process.env.REDIS_URL ?? '',
290
- budget: 25,
291
- windowSeconds: 86400,
190
+ loopSimilarityThreshold: 0.9,
191
+ loopMinRepeats: 3,
192
+ scope: { sessionId: 'agent-run-123' },
292
193
  });
293
-
294
- await pro.checkAndCharge('production', 0.0042);
295
- await pro.shutdown();
296
- ```
297
-
298
- `ioredis` is an optional dependency and is not loaded by the root import.
299
-
300
- `licenseKey` is accepted as a deprecated compatibility field only. AI CostGuard does not enforce commercial licenses locally, and `validateLicense()` is a format sanity helper, not security.
301
-
302
- ## CLI
303
-
304
- ```bash
305
- aifw check --budget 1 --model gpt-4o-mini --input-tokens 500 --tokens 1000 --max-steps 5
306
- ```
307
-
308
- The package also installs an `ai-costguard` bin alias:
309
-
310
- ```bash
311
- ai-costguard check --budget 1 --model gpt-4o-mini --tokens 1000 --max-steps 5
312
- ai-costguard dashboard --events .ai-costguard/events.jsonl --budget 5
313
- ```
314
-
315
- For custom models:
316
-
317
- ```bash
318
- aifw check --budget 1 --model internal-model --tokens 1000 --input-price-per-1k 0.001 --output-price-per-1k 0.002
319
194
  ```
320
195
 
321
- Exit codes:
196
+ Loop detection is heuristic. Expect false positives and false negatives, especially for short prompts, templated prompts, and prompts that share a lot of boilerplate.
322
197
 
323
- - `0`: projected cost is within budget
324
- - `1`: projected cost exceeds budget
325
- - `2`: usage/config error
326
-
327
- ## Benchmarks
328
-
329
- Run local benchmarks:
198
+ ## Accounting Semantics
199
+
200
+ AI CostGuard is a pre-call estimator, not a billing ledger.
201
+
202
+ - `attemptedCost`: estimated cost of every guarded attempt, including blocked attempts.
203
+ - `totalCost`: estimated cost of allowed calls.
204
+ - `blockedCost`: estimated cost stopped before provider execution.
205
+ - `actualCost`: provider-reported usage cost when the response includes recognizable `usage` fields.
206
+
207
+ Budget decisions use estimated allowed cost. Actual usage is recorded for observability but does not rewrite earlier decisions.
208
+
209
+ ## Pricing
330
210
 
331
- ```bash
332
- npm run build
333
- npm run benchmark
334
- ```
211
+ Known model pricing comes from built-in registry entries, runtime registrations, or per-guard overrides. Unknown models are blocked by default.
335
212
 
213
+ Pricing last updated: `2026-06-07`. Provider pricing changes; AI CostGuard does not fetch real-time pricing. Override pricing manually when provider pages or your contract pricing differ from the built-ins.
214
+
215
+ ```ts
216
+ import { registerPricing } from '@salimassili/ai-costguard';
217
+
218
+ registerPricing([
219
+ {
220
+ model: 'my-company-model',
221
+ inputPer1kTokens: 0.001,
222
+ outputPer1kTokens: 0.002,
223
+ lastUpdated: '2026-06-07',
224
+ source: 'internal',
225
+ },
226
+ ]);
227
+ ```
228
+
229
+ If you intentionally want fallback pricing for unknown models:
230
+
231
+ ```ts
232
+ guard(client, {
233
+ budget: 5,
234
+ unknownModelPolicy: 'fallback',
235
+ unknownModelPricing: {
236
+ model: 'fallback',
237
+ inputPer1kTokens: 0.001,
238
+ outputPer1kTokens: 0.002,
239
+ lastUpdated: '2026-06-07',
240
+ source: 'application fallback',
241
+ },
242
+ });
243
+ ```
244
+
245
+ Pricing changes frequently. Verify provider pricing before production use and override entries when needed.
246
+
247
+ ## Events
248
+
249
+ ```ts
250
+ const unsubscribe = openai.on('block', (event) => {
251
+ console.log(event.code, event.reason, event.context.estimatedCost);
252
+ });
253
+
254
+ unsubscribe();
255
+ ```
256
+
257
+ Supported events are `cost`, `allow`, and `block`. Handler errors are swallowed so observability code cannot change guard decisions.
258
+
259
+ ## Local Dashboard
260
+
261
+ Opt into a local JSONL event log:
262
+
263
+ ```ts
264
+ const openai = guard(client, {
265
+ budget: 5,
266
+ eventLogPath: '.ai-costguard/events.jsonl',
267
+ });
268
+ ```
269
+
270
+ Start the local-only dashboard:
271
+
272
+ ```bash
273
+ ai-costguard dashboard --events .ai-costguard/events.jsonl --budget 5
274
+ ```
275
+
276
+ For one-off package execution:
277
+
278
+ ```bash
279
+ npx @salimassili/ai-costguard dashboard --events .ai-costguard/events.jsonl --budget 5
280
+ ```
281
+
282
+ If the package is installed locally, `npx ai-costguard dashboard` also works. The dashboard binds to `127.0.0.1` by default and reads only local event files.
283
+
284
+ For CI or terminal output:
285
+
286
+ ```bash
287
+ ai-costguard dashboard --events .ai-costguard/events.jsonl --budget 5 --once --json
288
+ ```
289
+
290
+ See `docs/DASHBOARD.md`.
291
+
292
+ ## Integrations
293
+
294
+ Runnable mocked examples are included for:
295
+
296
+ - OpenAI SDK agent loop protection
297
+ - Anthropic SDK workflow budget guard
298
+ - Vercel AI SDK chatbot budget cap
299
+ - LangChain retry-storm prevention
300
+ - Mastra-style agent runner protection
301
+ - CrewAI launch/budget gate
302
+ - CI budget checks
303
+
304
+ See `docs/INTEGRATIONS.md` and `examples/integrations`.
305
+
306
+ ## Express Middleware
307
+
308
+ The middleware attaches a manual checker. It does not automatically parse or inspect every route.
309
+
310
+ ```ts
311
+ import { middleware, GuardError } from '@salimassili/ai-costguard';
312
+
313
+ app.use(middleware({ budget: 2 }));
314
+
315
+ app.post('/chat', async (req, res, next) => {
316
+ try {
317
+ req.localSafety.check({
318
+ model: 'gpt-4o-mini',
319
+ tokens: 500,
320
+ inputTokens: 100,
321
+ outputTokens: 400,
322
+ estimatedCost: 0.0003,
323
+ timestamp: Date.now(),
324
+ prompt: String(req.body?.prompt ?? ''),
325
+ });
326
+
327
+ res.json({ ok: true });
328
+ } catch (error) {
329
+ if (error instanceof GuardError) {
330
+ res.status(403).json({ code: error.code, reason: error.message });
331
+ return;
332
+ }
333
+ next(error);
334
+ }
335
+ });
336
+ ```
337
+
338
+ ## Optional Redis / Pro Helper
339
+
340
+ Redis-backed shared spend tracking is isolated behind a subpath import:
341
+
342
+ ```ts
343
+ import { GuardPro } from '@salimassili/ai-costguard/pro';
344
+
345
+ const pro = new GuardPro({
346
+ redisUrl: process.env.REDIS_URL ?? '',
347
+ budget: 25,
348
+ windowSeconds: 86400,
349
+ });
350
+
351
+ await pro.checkAndCharge('production', 0.0042);
352
+ await pro.shutdown();
353
+ ```
354
+
355
+ `ioredis` is an optional dependency and is not loaded by the root import.
356
+
357
+ AI CostGuard does not include license-key checks or local commercial-license enforcement.
358
+
359
+ ## CLI
360
+
361
+ ```bash
362
+ aifw check --budget 1 --model gpt-4o-mini --input-tokens 500 --tokens 1000 --max-steps 5
363
+ ```
364
+
365
+ The package also installs an `ai-costguard` bin alias:
366
+
367
+ ```bash
368
+ ai-costguard check --budget 1 --model gpt-4o-mini --tokens 1000 --max-steps 5
369
+ ai-costguard dashboard --events .ai-costguard/events.jsonl --budget 5
370
+ ```
371
+
372
+ For custom models:
373
+
374
+ ```bash
375
+ aifw check --budget 1 --model internal-model --tokens 1000 --input-price-per-1k 0.001 --output-price-per-1k 0.002
376
+ ```
377
+
378
+ Exit codes:
379
+
380
+ - `0`: projected cost is within budget
381
+ - `1`: projected cost exceeds budget
382
+ - `2`: usage/config error
383
+
384
+ ## Benchmarks
385
+
386
+ Run local benchmarks:
387
+
388
+ ```bash
389
+ npm run build
390
+ npm run benchmark
391
+ ```
392
+
336
393
  The script reports runtime overhead, approximate heap delta, false-positive scenarios, loop detection behavior, and cost-estimation boundaries. Results are local measurements, not universal guarantees. See `docs/BENCHMARKS.md`.
337
394
 
338
395
  Latest local benchmark in this repo on Node `v24.14.1` / Windows measured `0.020691 ms` added per mocked guarded call over `5000` iterations. Re-run on your target runtime before using this number in performance-sensitive claims.
339
396
 
340
- ## Why Not 50 Lines Of Code?
341
-
342
- A simple homemade budget check can stop one request after one counter crosses one number. AI CostGuard packages the parts that usually become messy once agents enter production:
343
-
344
- - Provider pricing registry with runtime overrides and unknown-model blocking.
345
- - Structured `GuardError` codes and metadata for API responses.
346
- - Scoped budget and behavior state per project, user, or session.
347
- - TTL-bounded prompt history.
348
- - Loop and retry-storm detection.
349
- - Estimated, attempted, blocked, and actual usage accounting.
350
- - Method filtering so non-AI SDK calls are not charged.
351
- - Event hooks, best-effort webhooks, JSONL event logs, and local dashboard visibility.
352
- - CI budget checks and runnable integration examples.
353
-
354
- ## Development
355
-
356
- ```bash
357
- npm ci
358
- npm run build
359
- npm run typecheck
360
- npm test
361
- npm run smoke
362
- npm run benchmark
363
- npm audit --omit=dev
364
- npm pack --dry-run
365
- ```
366
-
367
- ## Limitations
368
-
397
+ Token accuracy benchmark, fixed corpus, `gpt-tokenizer cl100k_base` fixture counts: average error `259.08%`, median error `263.98%`, max error `323.53%`, `8` samples. The current estimator is conservative and can substantially overestimate short prompts. Use this package as a pre-call guardrail, not an exact tokenizer.
398
+
399
+ ## Why Not 50 Lines Of Code?
400
+
401
+ A simple homemade budget check can stop one request after one counter crosses one number. AI CostGuard packages the parts that usually become messy once agents enter production:
402
+
403
+ - Provider pricing registry with runtime overrides and unknown-model blocking.
404
+ - Structured `GuardError` codes and metadata for API responses.
405
+ - Scoped budget and behavior state per project, user, or session.
406
+ - TTL-bounded prompt history.
407
+ - Loop and retry-storm detection.
408
+ - Estimated, attempted, blocked, and actual usage accounting.
409
+ - Method filtering so non-AI SDK calls are not charged.
410
+ - Event hooks, best-effort webhooks, JSONL event logs, and local dashboard visibility.
411
+ - CI budget checks and runnable integration examples.
412
+
413
+ ## Development
414
+
415
+ ```bash
416
+ npm ci
417
+ npm run build
418
+ npm run typecheck
419
+ npm test
420
+ npm run smoke
421
+ npm run benchmark
422
+ npm audit --omit=dev
423
+ npm pack --dry-run
424
+ ```
425
+
426
+ ## Limitations
427
+
369
428
  - Token counting is approximate and dependency-free.
429
+ - Token estimation is intentionally conservative and can overestimate materially; see the token accuracy benchmark.
370
430
  - Pricing entries can become stale; override them for production.
371
- - The free guard is process-local.
372
- - Loop detection uses character trigram similarity, not embeddings.
373
- - Retry detection is heuristic.
374
- - Webhooks are best-effort and never affect enforcement.
375
- - The dashboard reads local JSONL logs only; it is not a hosted analytics product.
376
- - Provider usage reconciliation only works when responses expose recognizable `usage` fields.
377
-
378
- ## License
379
-
380
- MIT
431
+ - The free guard is process-local.
432
+ - Loop detection uses character trigram similarity, not embeddings.
433
+ - Retry detection is heuristic.
434
+ - Webhooks are best-effort and never affect enforcement.
435
+ - The dashboard reads local JSONL logs only; it is not a hosted analytics product.
436
+ - Provider usage reconciliation only works when responses expose recognizable `usage` fields.
437
+
438
+ ## License
439
+
440
+ MIT