@salimassili/ai-costguard 1.1.8 → 1.2.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.
- package/README.md +156 -61
- package/dist/core/CostGuard.d.ts +6 -26
- package/dist/core/CostGuard.d.ts.map +1 -1
- package/dist/core/CostGuard.js +3 -190
- package/dist/core/CostGuard.js.map +1 -1
- package/dist/core/GuardCore.d.ts +22 -0
- package/dist/core/GuardCore.d.ts.map +1 -0
- package/dist/core/GuardCore.js +176 -0
- package/dist/core/GuardCore.js.map +1 -0
- package/dist/core/GuardFree.d.ts +27 -0
- package/dist/core/GuardFree.d.ts.map +1 -0
- package/dist/core/GuardFree.js +159 -0
- package/dist/core/GuardFree.js.map +1 -0
- package/dist/core/GuardPro.d.ts +27 -0
- package/dist/core/GuardPro.d.ts.map +1 -0
- package/dist/core/GuardPro.js +182 -0
- package/dist/core/GuardPro.js.map +1 -0
- package/dist/core/types.d.ts +6 -13
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +1 -1
- package/dist/index.d.ts +6 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -19
- package/dist/index.js.map +1 -1
- package/dist/pricing/index.d.ts +11 -0
- package/dist/pricing/index.d.ts.map +1 -0
- package/dist/pricing/index.js +103 -0
- package/dist/pricing/index.js.map +1 -0
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -1,107 +1,202 @@
|
|
|
1
1
|
# AI CostGuard
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
AI CostGuard is a small TypeScript library that wraps OpenAI-like clients and blocks requests before they run when local safety checks predict unsafe AI API spend.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
It is ESM-only, targets Node.js 18+, and is built with `tsc`.
|
|
6
|
+
|
|
7
|
+
## What Works Today
|
|
8
|
+
|
|
9
|
+
- `guard()` wraps a client with budget, loop, and retry protection.
|
|
10
|
+
- `GuardError` is thrown when a request is blocked.
|
|
11
|
+
- Budget blocking estimates request cost before the API call.
|
|
12
|
+
- Loop detection blocks repeated prompts within the current process.
|
|
13
|
+
- Retry detection blocks repeated failure/retry prompts within the current process.
|
|
14
|
+
- `middleware()` adds the same local checks to web request flows.
|
|
15
|
+
- `getPricing()` returns known built-in model pricing.
|
|
16
|
+
- `registerPricing()` and `listPricing()` let you manage runtime pricing entries.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
6
19
|
|
|
7
20
|
```bash
|
|
8
21
|
npm install @salimassili/ai-costguard
|
|
9
22
|
```
|
|
10
23
|
|
|
11
|
-
##
|
|
24
|
+
## Basic Usage
|
|
12
25
|
|
|
13
26
|
```ts
|
|
14
|
-
import
|
|
27
|
+
import OpenAI from 'openai';
|
|
28
|
+
import { guard, GuardError } from '@salimassili/ai-costguard';
|
|
15
29
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
30
|
+
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
|
31
|
+
const ai = guard(client, { budget: 1 });
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const response = await ai.chat.completions.create({
|
|
35
|
+
model: 'gpt-4o-mini',
|
|
36
|
+
messages: [{ role: 'user', content: 'Write a short project summary.' }],
|
|
37
|
+
max_tokens: 200
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
console.log(response);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error instanceof GuardError) {
|
|
43
|
+
console.error('AI request blocked:', error.message, error.context);
|
|
44
|
+
} else {
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
19
48
|
```
|
|
20
49
|
|
|
21
|
-
##
|
|
50
|
+
## How `guard()` Works
|
|
22
51
|
|
|
23
|
-
|
|
52
|
+
`guard(client, config)` returns a `Proxy` around your client. When code calls a method such as `client.chat.completions.create(...)`, CostGuard:
|
|
24
53
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
54
|
+
1. Reads the request model, messages, and `max_tokens`.
|
|
55
|
+
2. Estimates input tokens from message length and combines them with the requested output limit.
|
|
56
|
+
3. Looks up pricing for the model.
|
|
57
|
+
4. Estimates the request cost.
|
|
58
|
+
5. Blocks the call with `GuardError` if the local budget would be exceeded.
|
|
59
|
+
6. Blocks repeated prompts that look like loops.
|
|
60
|
+
7. Blocks repeated prompts that look like retry storms.
|
|
61
|
+
8. Lets the original client method run when checks pass.
|
|
28
62
|
|
|
29
|
-
|
|
63
|
+
The free guard state is process-local. Separate Node.js processes do not share budget state.
|
|
30
64
|
|
|
31
|
-
##
|
|
65
|
+
## Budget Blocking
|
|
32
66
|
|
|
33
|
-
|
|
67
|
+
```ts
|
|
68
|
+
import { guard } from '@salimassili/ai-costguard';
|
|
34
69
|
|
|
35
|
-
|
|
70
|
+
const ai = guard(openai, { budget: 0.25 });
|
|
36
71
|
|
|
37
|
-
|
|
72
|
+
await ai.chat.completions.create({
|
|
73
|
+
model: 'gpt-4o-mini',
|
|
74
|
+
messages: [{ role: 'user', content: 'Hello' }],
|
|
75
|
+
max_tokens: 100
|
|
76
|
+
});
|
|
77
|
+
```
|
|
38
78
|
|
|
39
|
-
|
|
79
|
+
When the estimated cumulative spend in the current process would exceed `budget`, CostGuard throws `GuardError` before calling the underlying AI client.
|
|
40
80
|
|
|
41
|
-
##
|
|
81
|
+
## Loop And Retry Detection
|
|
42
82
|
|
|
43
|
-
|
|
44
|
-
Agent repeats the same reasoning forever.
|
|
83
|
+
CostGuard keeps a short in-memory history of recent prompts for the wrapped client:
|
|
45
84
|
|
|
46
|
-
|
|
47
|
-
|
|
85
|
+
- Loop detection blocks repeated prompt hashes.
|
|
86
|
+
- Retry detection blocks repeated prompts containing retry/failure language such as `retry`, `again`, `repeat`, `error`, `fail`, or `timeout`.
|
|
48
87
|
|
|
49
|
-
|
|
50
|
-
Agent burns tokens rapidly until blocked.
|
|
88
|
+
These checks are intentionally local and lightweight.
|
|
51
89
|
|
|
52
|
-
##
|
|
90
|
+
## Middleware
|
|
53
91
|
|
|
54
92
|
```ts
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
})
|
|
93
|
+
import express from 'express';
|
|
94
|
+
import { middleware, GuardError } from '@salimassili/ai-costguard';
|
|
95
|
+
|
|
96
|
+
const app = express();
|
|
97
|
+
|
|
98
|
+
app.use(middleware({ budget: 2 }));
|
|
99
|
+
|
|
100
|
+
app.post('/chat', async (req, res, next) => {
|
|
101
|
+
try {
|
|
102
|
+
req.localSafety.check({
|
|
103
|
+
model: 'gpt-4o-mini',
|
|
104
|
+
tokens: 1000,
|
|
105
|
+
estimatedCost: 0.001,
|
|
106
|
+
timestamp: Date.now(),
|
|
107
|
+
prompt: req.body?.prompt ?? ''
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
res.json({ ok: true });
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (error instanceof GuardError) {
|
|
113
|
+
res.status(402).json({ error: error.message, context: error.context });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
next(error);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
61
120
|
```
|
|
62
121
|
|
|
63
|
-
##
|
|
122
|
+
## Pricing
|
|
64
123
|
|
|
65
124
|
```ts
|
|
66
|
-
import {
|
|
125
|
+
import { getPricing, listPricing, registerPricing } from '@salimassili/ai-costguard';
|
|
67
126
|
|
|
68
|
-
|
|
69
|
-
new OpenAI({ apiKey: process.env.OPENAI_API_KEY }),
|
|
70
|
-
{ maxTotalCostPerDay: 5 }
|
|
71
|
-
);
|
|
127
|
+
console.log(getPricing('gpt-4o-mini'));
|
|
72
128
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
model: '
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
console.log('Agent blocked:', err.message);
|
|
81
|
-
// Handle gracefully
|
|
129
|
+
registerPricing([
|
|
130
|
+
{
|
|
131
|
+
model: 'custom-model',
|
|
132
|
+
inputPer1kTokens: 0.001,
|
|
133
|
+
outputPer1kTokens: 0.002,
|
|
134
|
+
lastUpdated: '2026-05-21',
|
|
135
|
+
source: 'internal'
|
|
82
136
|
}
|
|
83
|
-
|
|
137
|
+
]);
|
|
138
|
+
|
|
139
|
+
console.log(listPricing());
|
|
84
140
|
```
|
|
85
141
|
|
|
86
|
-
|
|
142
|
+
`getPricing(model)` returns an exact match when available, then falls back to simple fuzzy matching. Unknown models return `undefined`.
|
|
143
|
+
|
|
144
|
+
## Pro Features (Coming Soon)
|
|
145
|
+
|
|
146
|
+
> These features are under active development and not yet available:
|
|
147
|
+
> - Distributed Redis-backed budget state
|
|
148
|
+
> - Real Slack/Discord webhook alerts
|
|
149
|
+
> - Multi-instance coordination
|
|
150
|
+
> - Production license validation
|
|
151
|
+
|
|
152
|
+
## API
|
|
153
|
+
|
|
154
|
+
### `guard(client, config)`
|
|
155
|
+
|
|
156
|
+
Wraps an OpenAI-like client.
|
|
87
157
|
|
|
88
158
|
```ts
|
|
89
|
-
|
|
159
|
+
guard(client, { budget: 10 });
|
|
160
|
+
```
|
|
90
161
|
|
|
91
|
-
|
|
162
|
+
### `GuardError`
|
|
163
|
+
|
|
164
|
+
Thrown when CostGuard blocks a request.
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
try {
|
|
168
|
+
await ai.chat.completions.create(params);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
if (error instanceof GuardError) {
|
|
171
|
+
console.log(error.context);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
92
174
|
```
|
|
93
175
|
|
|
94
|
-
|
|
176
|
+
### `middleware(config)`
|
|
95
177
|
|
|
96
|
-
|
|
97
|
-
- **Daily cost caps** — hard USD limits, no overages
|
|
98
|
-
- **Token limits** — block oversized single requests
|
|
99
|
-
- **RPM limits** — catch agents hammering the API
|
|
178
|
+
Creates request middleware with local budget, loop, and retry checks.
|
|
100
179
|
|
|
101
|
-
|
|
180
|
+
### `getPricing(model, overrides?)`
|
|
102
181
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
182
|
+
Returns pricing for a model from overrides, runtime registrations, or built-in entries.
|
|
183
|
+
|
|
184
|
+
### `registerPricing(entries)`
|
|
185
|
+
|
|
186
|
+
Registers or replaces runtime pricing entries by model name.
|
|
187
|
+
|
|
188
|
+
### `listPricing()`
|
|
189
|
+
|
|
190
|
+
Returns built-in and runtime pricing entries, deduplicated by model name.
|
|
191
|
+
|
|
192
|
+
## Limitations
|
|
193
|
+
|
|
194
|
+
- Free guard state is stored in memory only.
|
|
195
|
+
- Budget checks are estimates, not billing records.
|
|
196
|
+
- Token estimation is approximate.
|
|
197
|
+
- Pricing entries are static until the package or runtime registry is updated.
|
|
198
|
+
- The library does not include dashboards, analytics, governance workflows, or hosted services.
|
|
199
|
+
|
|
200
|
+
## License
|
|
106
201
|
|
|
107
|
-
MIT
|
|
202
|
+
MIT
|
package/dist/core/CostGuard.d.ts
CHANGED
|
@@ -1,27 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { CostGuardConfig, RequestContext, GuardState } from './types.js';
|
|
8
|
-
/**
|
|
9
|
-
* Wraps any AI client. Detects loops. Enforces hard limits.
|
|
10
|
-
* Throws on block so your app handles it.
|
|
11
|
-
*/
|
|
12
|
-
export declare function withCostGuard(client: any, config: any, sharedState?: GuardState): any;
|
|
13
|
-
/**
|
|
14
|
-
* Standalone middleware for Express/Fastify
|
|
15
|
-
*/
|
|
16
|
-
export declare function costGuardMiddleware(config?: Partial<CostGuardConfig>): (req: any, res: any, next: any) => void;
|
|
17
|
-
/** Custom error so users can catch it specifically */
|
|
18
|
-
export declare class CostGuardError extends Error {
|
|
19
|
-
context: RequestContext;
|
|
20
|
-
constructor(message: string, context: RequestContext);
|
|
21
|
-
}
|
|
22
|
-
/** Get current pricing for a model (cents per 1K tokens) */
|
|
23
|
-
export declare function getPricing(model: string): {
|
|
24
|
-
input: number;
|
|
25
|
-
output: number;
|
|
26
|
-
} | undefined;
|
|
1
|
+
export { guard, GuardError, middleware } from './GuardFree.js';
|
|
2
|
+
export { GuardPro, validateLicense, getProGuard } from './GuardPro.js';
|
|
3
|
+
export type { GuardProConfig } from './GuardPro.js';
|
|
4
|
+
export { getPricing, registerPricing, listPricing } from '../pricing/index.js';
|
|
5
|
+
export type { ModelPricing } from '../pricing/index.js';
|
|
6
|
+
export type { GuardConfig } from './types.js';
|
|
27
7
|
//# sourceMappingURL=CostGuard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CostGuard.d.ts","sourceRoot":"","sources":["../../src/core/CostGuard.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"CostGuard.d.ts","sourceRoot":"","sources":["../../src/core/CostGuard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACvE,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAC/E,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/core/CostGuard.js
CHANGED
|
@@ -1,191 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Wraps your AI client and kills runaway loops before they burn money.
|
|
5
|
-
* When it blocks, it tells you exactly how much it saved.
|
|
6
|
-
*/
|
|
7
|
-
const MODEL_PRICING = {
|
|
8
|
-
'gpt-4': { input: 0.03, output: 0.06 },
|
|
9
|
-
'gpt-4o': { input: 0.005, output: 0.015 },
|
|
10
|
-
'gpt-4o-mini': { input: 0.00015, output: 0.0006 },
|
|
11
|
-
'gpt-3.5-turbo': { input: 0.0005, output: 0.0015 },
|
|
12
|
-
'claude-3-opus': { input: 0.015, output: 0.075 },
|
|
13
|
-
'claude-3-sonnet': { input: 0.003, output: 0.015 },
|
|
14
|
-
'claude-3-haiku': { input: 0.00025, output: 0.00125 },
|
|
15
|
-
};
|
|
16
|
-
/**
|
|
17
|
-
* Wraps any AI client. Detects loops. Enforces hard limits.
|
|
18
|
-
* Throws on block so your app handles it.
|
|
19
|
-
*/
|
|
20
|
-
export function withCostGuard(client, config, sharedState) {
|
|
21
|
-
const defaults = {
|
|
22
|
-
maxTokensPerRequest: 4000,
|
|
23
|
-
maxRequestsPerMinute: 30,
|
|
24
|
-
maxTotalCostPerDay: 10.00,
|
|
25
|
-
loopDetection: true,
|
|
26
|
-
};
|
|
27
|
-
const guard = { ...defaults, ...config };
|
|
28
|
-
// Use shared state if provided (for nested objects), otherwise create new
|
|
29
|
-
const state = sharedState || {
|
|
30
|
-
requestCount: 0,
|
|
31
|
-
totalCost: 0,
|
|
32
|
-
lastRequestTime: 0,
|
|
33
|
-
recentPrompts: [],
|
|
34
|
-
blockedCount: 0,
|
|
35
|
-
};
|
|
36
|
-
return new Proxy(client, {
|
|
37
|
-
get(target, prop) {
|
|
38
|
-
const value = target[prop];
|
|
39
|
-
// If it's a function (like .create), wrap it
|
|
40
|
-
if (typeof value === 'function') {
|
|
41
|
-
return (...args) => {
|
|
42
|
-
// Extract request details from args
|
|
43
|
-
const ctx = extractContext(args, prop);
|
|
44
|
-
// Evaluate against limits
|
|
45
|
-
const decision = evaluate(guard, state, ctx);
|
|
46
|
-
if (decision === 'block') {
|
|
47
|
-
state.blockedCount++;
|
|
48
|
-
// Build a financial save message
|
|
49
|
-
const saveMsg = buildSaveMessage(state, ctx);
|
|
50
|
-
console.error(saveMsg);
|
|
51
|
-
if (guard.onLimitHit)
|
|
52
|
-
guard.onLimitHit('Limit exceeded', ctx.estimatedCost);
|
|
53
|
-
throw new CostGuardError(saveMsg.replace('[AI CostGuard] ', ''), ctx);
|
|
54
|
-
}
|
|
55
|
-
// Update state
|
|
56
|
-
state.requestCount++;
|
|
57
|
-
state.totalCost += ctx.estimatedCost;
|
|
58
|
-
state.lastRequestTime = Date.now();
|
|
59
|
-
if (guard.loopDetection)
|
|
60
|
-
state.recentPrompts.push(ctx.prompt);
|
|
61
|
-
if (state.recentPrompts.length > 20)
|
|
62
|
-
state.recentPrompts.shift();
|
|
63
|
-
console.log(`[AI CostGuard] ALLOW: ${ctx.model} | ${ctx.tokens}t | $${ctx.estimatedCost.toFixed(4)} | Total: $${state.totalCost.toFixed(2)}`);
|
|
64
|
-
// Pass through to actual API call
|
|
65
|
-
return value.apply(target, args);
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
// If it's a nested object (like .chat.completions), recurse with shared state
|
|
69
|
-
if (value && typeof value === 'object') {
|
|
70
|
-
return withCostGuard(value, guard, state);
|
|
71
|
-
}
|
|
72
|
-
return value;
|
|
73
|
-
},
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Standalone middleware for Express/Fastify
|
|
78
|
-
*/
|
|
79
|
-
export function costGuardMiddleware(config = {}) {
|
|
80
|
-
const guard = {
|
|
81
|
-
maxTokensPerRequest: 4000,
|
|
82
|
-
maxRequestsPerMinute: 30,
|
|
83
|
-
maxTotalCostPerDay: 10.00,
|
|
84
|
-
loopDetection: true,
|
|
85
|
-
...config,
|
|
86
|
-
};
|
|
87
|
-
const state = {
|
|
88
|
-
requestCount: 0,
|
|
89
|
-
totalCost: 0,
|
|
90
|
-
lastRequestTime: 0,
|
|
91
|
-
recentPrompts: [],
|
|
92
|
-
blockedCount: 0,
|
|
93
|
-
};
|
|
94
|
-
return (req, res, next) => {
|
|
95
|
-
req.costGuard = {
|
|
96
|
-
state,
|
|
97
|
-
evaluate: (ctx) => {
|
|
98
|
-
const decision = evaluate(guard, state, ctx);
|
|
99
|
-
if (decision === 'block') {
|
|
100
|
-
state.blockedCount++;
|
|
101
|
-
throw new CostGuardError('Request blocked by cost guard', ctx);
|
|
102
|
-
}
|
|
103
|
-
state.requestCount++;
|
|
104
|
-
state.totalCost += ctx.estimatedCost;
|
|
105
|
-
state.lastRequestTime = Date.now();
|
|
106
|
-
},
|
|
107
|
-
};
|
|
108
|
-
next();
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
/** Extract request context from API call args */
|
|
112
|
-
function extractContext(args, prop) {
|
|
113
|
-
const params = args[0] || {};
|
|
114
|
-
const model = params.model || 'unknown';
|
|
115
|
-
const messages = params.messages || [];
|
|
116
|
-
const prompt = messages.map((m) => m.content).join(' ').slice(0, 200);
|
|
117
|
-
// Estimate tokens (rough: 1 token ≈ 4 chars for English)
|
|
118
|
-
const inputText = JSON.stringify(messages);
|
|
119
|
-
const estimatedInputTokens = Math.ceil(inputText.length / 4);
|
|
120
|
-
const maxOutputTokens = params.max_tokens || 1000;
|
|
121
|
-
const tokens = estimatedInputTokens + maxOutputTokens;
|
|
122
|
-
// Estimate cost
|
|
123
|
-
const pricing = MODEL_PRICING[model] || { input: 0.01, output: 0.03 };
|
|
124
|
-
const estimatedCost = (estimatedInputTokens / 1000) * pricing.input +
|
|
125
|
-
(maxOutputTokens / 1000) * pricing.output;
|
|
126
|
-
return {
|
|
127
|
-
model,
|
|
128
|
-
tokens,
|
|
129
|
-
estimatedCost,
|
|
130
|
-
timestamp: Date.now(),
|
|
131
|
-
prompt,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
/** Build a human-readable "financial save" message */
|
|
135
|
-
function buildSaveMessage(state, ctx) {
|
|
136
|
-
const duplicates = state.recentPrompts.filter(p => p === ctx.prompt).length;
|
|
137
|
-
const isLoop = duplicates >= 2;
|
|
138
|
-
// Estimate how much we saved by blocking this + projected future calls
|
|
139
|
-
// Assume a runaway loop would repeat ~50 more times if unchecked
|
|
140
|
-
const projectedCycles = isLoop ? 50 : 10;
|
|
141
|
-
const estimatedSave = (ctx.estimatedCost * projectedCycles) + ctx.estimatedCost;
|
|
142
|
-
if (isLoop) {
|
|
143
|
-
return `[AI CostGuard] BLOCKED LOOP → ${duplicates + 1} recursive cycles detected → estimated save: $${estimatedSave.toFixed(2)}`;
|
|
144
|
-
}
|
|
145
|
-
if (ctx.tokens > 4000) {
|
|
146
|
-
return `[AI CostGuard] BLOCKED TOKEN BOMB → ${ctx.tokens} tokens → estimated save: $${ctx.estimatedCost.toFixed(2)}`;
|
|
147
|
-
}
|
|
148
|
-
if (state.totalCost + ctx.estimatedCost > 5) {
|
|
149
|
-
const remaining = (state.totalCost + ctx.estimatedCost) - 5;
|
|
150
|
-
return `[AI CostGuard] BLOCKED BUDGET BREACH → daily cap reached → estimated save: $${remaining.toFixed(2)}`;
|
|
151
|
-
}
|
|
152
|
-
return `[AI CostGuard] BLOCKED → estimated save: $${ctx.estimatedCost.toFixed(2)}`;
|
|
153
|
-
}
|
|
154
|
-
/** Evaluate request against all limits */
|
|
155
|
-
function evaluate(guard, state, ctx) {
|
|
156
|
-
// 1. Token limit
|
|
157
|
-
if (ctx.tokens > guard.maxTokensPerRequest) {
|
|
158
|
-
return 'block';
|
|
159
|
-
}
|
|
160
|
-
// 2. RPM limit
|
|
161
|
-
const oneMinuteAgo = Date.now() - 60000;
|
|
162
|
-
if (state.lastRequestTime > oneMinuteAgo && state.requestCount >= guard.maxRequestsPerMinute) {
|
|
163
|
-
return 'block';
|
|
164
|
-
}
|
|
165
|
-
// 3. Daily cost limit
|
|
166
|
-
if (state.totalCost + ctx.estimatedCost > guard.maxTotalCostPerDay) {
|
|
167
|
-
return 'block';
|
|
168
|
-
}
|
|
169
|
-
// 4. Loop detection (simple: same prompt repeated)
|
|
170
|
-
if (guard.loopDetection && state.recentPrompts.length > 0) {
|
|
171
|
-
const duplicates = state.recentPrompts.filter(p => p === ctx.prompt).length;
|
|
172
|
-
if (duplicates >= 2) {
|
|
173
|
-
return 'block';
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return 'allow';
|
|
177
|
-
}
|
|
178
|
-
/** Custom error so users can catch it specifically */
|
|
179
|
-
export class CostGuardError extends Error {
|
|
180
|
-
context;
|
|
181
|
-
constructor(message, context) {
|
|
182
|
-
super(message);
|
|
183
|
-
this.name = 'CostGuardError';
|
|
184
|
-
this.context = context;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
/** Get current pricing for a model (cents per 1K tokens) */
|
|
188
|
-
export function getPricing(model) {
|
|
189
|
-
return MODEL_PRICING[model];
|
|
190
|
-
}
|
|
1
|
+
export { guard, GuardError, middleware } from './GuardFree.js';
|
|
2
|
+
export { GuardPro, validateLicense, getProGuard } from './GuardPro.js';
|
|
3
|
+
export { getPricing, registerPricing, listPricing } from '../pricing/index.js';
|
|
191
4
|
//# sourceMappingURL=CostGuard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CostGuard.js","sourceRoot":"","sources":["../../src/core/CostGuard.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"CostGuard.js","sourceRoot":"","sources":["../../src/core/CostGuard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEvE,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GuardCore.ts - FREE CORE (VIRAL ENGINE)
|
|
3
|
+
*
|
|
4
|
+
* Maximum simplicity. Maximum virality. Zero complexity.
|
|
5
|
+
* Instant safety layer every AI developer installs by default.
|
|
6
|
+
*/
|
|
7
|
+
import { GuardConfig, RequestContext, GuardState } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Guard your AI client from wasting money
|
|
10
|
+
* FREE CORE: Local protection + viral logs + real-time risk warnings
|
|
11
|
+
*/
|
|
12
|
+
export declare function guard(client: any, config: GuardConfig, sharedState?: GuardState): any;
|
|
13
|
+
/**
|
|
14
|
+
* Express middleware (FREE CORE - LOCAL ONLY)
|
|
15
|
+
*/
|
|
16
|
+
export declare function middleware(config: GuardConfig): (req: any, res: any, next: any) => void;
|
|
17
|
+
export declare class GuardError extends Error {
|
|
18
|
+
context: RequestContext;
|
|
19
|
+
constructor(message: string, context: RequestContext);
|
|
20
|
+
}
|
|
21
|
+
export { getPricing } from '../pricing/index.js';
|
|
22
|
+
//# sourceMappingURL=GuardCore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GuardCore.d.ts","sourceRoot":"","sources":["../../src/core/GuardCore.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAErE;;;GAGG;AACH,wBAAgB,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,UAAU,OAgF/E;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,WAAW,IAGpC,KAAK,GAAG,EAAE,KAAK,GAAG,EAAE,MAAM,GAAG,UAiCtC;AAiED,qBAAa,UAAW,SAAQ,KAAK;IACnC,OAAO,EAAE,cAAc,CAAC;gBACZ,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc;CAKrD;AAED,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC"}
|