@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.
- package/CHANGELOG.md +59 -50
- package/LICENSE +21 -21
- package/README.md +399 -339
- package/benchmarks/run.mjs +229 -229
- package/benchmarks/token-accuracy.mjs +86 -0
- package/dist/core/CostGuard.d.ts +1 -1
- package/dist/core/CostGuard.d.ts.map +1 -1
- package/dist/core/CostGuard.js +1 -1
- package/dist/core/CostGuard.js.map +1 -1
- package/dist/core/GuardPro.d.ts +1 -13
- package/dist/core/GuardPro.d.ts.map +1 -1
- package/dist/core/GuardPro.js +7 -19
- package/dist/core/GuardPro.js.map +1 -1
- package/dist/core/types.d.ts +1 -3
- package/dist/core/types.d.ts.map +1 -1
- package/dist/dashboard.js +49 -49
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/pricing/index.d.ts +7 -0
- package/dist/pricing/index.d.ts.map +1 -1
- package/dist/pricing/index.js +7 -0
- package/dist/pricing/index.js.map +1 -1
- package/dist/pro.d.ts +1 -1
- package/dist/pro.d.ts.map +1 -1
- package/dist/pro.js +1 -1
- package/dist/pro.js.map +1 -1
- package/docs/BENCHMARKS.md +60 -41
- package/docs/DASHBOARD.md +61 -61
- package/docs/INTEGRATIONS.md +153 -153
- package/examples/integrations/anthropic-workflow-budget.mjs +36 -36
- package/examples/integrations/ci-budget-check.mjs +32 -32
- package/examples/integrations/crewai-budget-gate.mjs +31 -31
- package/examples/integrations/langchain-retry-storm.mjs +32 -32
- package/examples/integrations/mastra-agent.mjs +41 -41
- package/examples/integrations/openai-agent-loop.mjs +44 -44
- package/examples/integrations/vercel-ai-chatbot.mjs +29 -29
- package/package.json +71 -69
package/README.md
CHANGED
|
@@ -1,380 +1,440 @@
|
|
|
1
|
-
# AI CostGuard
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
# AI CostGuard
|
|
2
|
+
[](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
|
-
##
|
|
8
|
+
## What AI CostGuard Does
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
##
|
|
16
|
+
## What AI CostGuard Does Not Do
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
61
|
+
Without AI CostGuard:
|
|
52
62
|
|
|
53
63
|
```ts
|
|
54
|
-
|
|
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
|
-
|
|
67
|
+
With AI CostGuard:
|
|
70
68
|
|
|
71
69
|
```ts
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
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
|
-
##
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
180
|
+
Default loop detection uses character trigram cosine similarity with `loopSimilarityThreshold: 0.85` and `loopMinRepeats: 2`.
|
|
203
181
|
|
|
204
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
324
|
-
|
|
325
|
-
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
-
|
|
347
|
-
-
|
|
348
|
-
-
|
|
349
|
-
-
|
|
350
|
-
-
|
|
351
|
-
-
|
|
352
|
-
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
npm
|
|
360
|
-
npm
|
|
361
|
-
npm run
|
|
362
|
-
npm
|
|
363
|
-
npm
|
|
364
|
-
npm
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|