@salimassili/ai-costguard 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -0
- package/LICENSE +21 -0
- package/README.md +281 -103
- package/benchmarks/run.mjs +229 -0
- package/dist/cli.d.ts +50 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +178 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/CostGuard.d.ts +3 -4
- package/dist/core/CostGuard.d.ts.map +1 -1
- package/dist/core/CostGuard.js +1 -2
- package/dist/core/CostGuard.js.map +1 -1
- package/dist/core/GuardCore.d.ts +93 -13
- package/dist/core/GuardCore.d.ts.map +1 -1
- package/dist/core/GuardCore.js +372 -158
- package/dist/core/GuardCore.js.map +1 -1
- package/dist/core/GuardFree.d.ts +42 -18
- package/dist/core/GuardFree.d.ts.map +1 -1
- package/dist/core/GuardFree.js +95 -140
- package/dist/core/GuardFree.js.map +1 -1
- package/dist/core/GuardPro.d.ts +85 -5
- package/dist/core/GuardPro.d.ts.map +1 -1
- package/dist/core/GuardPro.js +216 -121
- package/dist/core/GuardPro.js.map +1 -1
- package/dist/core/event-log.d.ts +37 -0
- package/dist/core/event-log.d.ts.map +1 -0
- package/dist/core/event-log.js +49 -0
- package/dist/core/event-log.js.map +1 -0
- package/dist/core/events.d.ts +20 -0
- package/dist/core/events.d.ts.map +1 -0
- package/dist/core/events.js +46 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/similarity.d.ts +13 -0
- package/dist/core/similarity.d.ts.map +1 -0
- package/dist/core/similarity.js +51 -0
- package/dist/core/similarity.js.map +1 -0
- package/dist/core/tokenizer.d.ts +18 -0
- package/dist/core/tokenizer.d.ts.map +1 -0
- package/dist/core/tokenizer.js +137 -0
- package/dist/core/tokenizer.js.map +1 -0
- package/dist/core/types.d.ts +153 -5
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +0 -3
- package/dist/core/types.js.map +1 -1
- package/dist/core/webhooks.d.ts +15 -0
- package/dist/core/webhooks.d.ts.map +1 -0
- package/dist/core/webhooks.js +58 -0
- package/dist/core/webhooks.js.map +1 -0
- package/dist/dashboard.d.ts +73 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +201 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/index.d.ts +3 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/pricing/index.d.ts +19 -2
- package/dist/pricing/index.d.ts.map +1 -1
- package/dist/pricing/index.js +93 -13
- package/dist/pricing/index.js.map +1 -1
- package/dist/pro.d.ts +3 -0
- package/dist/pro.d.ts.map +1 -0
- package/dist/pro.js +2 -0
- package/dist/pro.js.map +1 -0
- package/docs/BENCHMARKS.md +51 -0
- package/docs/DASHBOARD.md +61 -0
- package/docs/INTEGRATIONS.md +153 -0
- package/examples/integrations/anthropic-workflow-budget.mjs +36 -0
- package/examples/integrations/ci-budget-check.mjs +32 -0
- package/examples/integrations/crewai-budget-gate.mjs +31 -0
- package/examples/integrations/langchain-retry-storm.mjs +32 -0
- package/examples/integrations/mastra-agent.mjs +41 -0
- package/examples/integrations/openai-agent-loop.mjs +44 -0
- package/examples/integrations/vercel-ai-chatbot.mjs +29 -0
- package/package.json +35 -7
package/dist/core/GuardFree.d.ts
CHANGED
|
@@ -1,27 +1,51 @@
|
|
|
1
|
+
import { GuardError } from './GuardCore.js';
|
|
2
|
+
import type { GuardConfig, GuardEventHandler, GuardEventName, GuardState, RequestContext } from './types.js';
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Each process thinks it is safe.
|
|
5
|
-
* Single-process protection only.
|
|
6
|
-
*
|
|
7
|
-
* INSTALL: const ai = guard(openai)
|
|
4
|
+
* Event controls added to a guarded client proxy.
|
|
8
5
|
*/
|
|
9
|
-
|
|
6
|
+
export interface GuardEventControls {
|
|
7
|
+
/** Subscribes to block, allow, or cost events. */
|
|
8
|
+
on(eventName: GuardEventName, handler: GuardEventHandler): () => void;
|
|
9
|
+
/** Removes an event handler. */
|
|
10
|
+
off(eventName: GuardEventName, handler: GuardEventHandler): void;
|
|
11
|
+
/** Returns the mutable process-local guard state. */
|
|
12
|
+
getGuardState(): GuardState;
|
|
13
|
+
}
|
|
10
14
|
/**
|
|
11
|
-
*
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
* Client type returned by guard().
|
|
16
|
+
*/
|
|
17
|
+
export type GuardedClient<TClient extends object> = TClient & GuardEventControls;
|
|
18
|
+
/**
|
|
19
|
+
* Wraps an OpenAI-like client with process-local cost, loop, and retry protection.
|
|
20
|
+
*/
|
|
21
|
+
export declare function guard<TClient extends object>(client: TClient, config?: GuardConfig, sharedState?: GuardState): GuardedClient<TClient>;
|
|
22
|
+
/**
|
|
23
|
+
* Wraps a standalone AI function with the same guard behavior as guard().
|
|
15
24
|
*
|
|
16
|
-
*
|
|
25
|
+
* The first function argument should be an OpenAI-like request object containing
|
|
26
|
+
* model, messages/prompt/input, and max_tokens/maxOutputTokens when possible.
|
|
27
|
+
*/
|
|
28
|
+
export declare function guardFunction<TArgs extends readonly unknown[], TResult>(fn: (...args: TArgs) => TResult, config?: GuardConfig): ((...args: TArgs) => TResult) & GuardEventControls;
|
|
29
|
+
/**
|
|
30
|
+
* Express-compatible middleware that attaches req.localSafety.check() and req.guard.check().
|
|
17
31
|
*/
|
|
18
|
-
export declare function
|
|
32
|
+
export declare function middleware(config?: GuardConfig): (req: MiddlewareRequest, res: unknown, next: () => void) => void;
|
|
19
33
|
/**
|
|
20
|
-
*
|
|
34
|
+
* Error thrown when a request is blocked before provider execution.
|
|
21
35
|
*/
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
36
|
+
export { GuardError };
|
|
37
|
+
/**
|
|
38
|
+
* Pricing lookup re-export kept for compatibility with older imports.
|
|
39
|
+
*/
|
|
40
|
+
export { getPricing } from '../pricing/index.js';
|
|
41
|
+
interface MiddlewareRequest {
|
|
42
|
+
localSafety?: MiddlewareControls;
|
|
43
|
+
guard?: MiddlewareControls;
|
|
44
|
+
}
|
|
45
|
+
interface MiddlewareControls {
|
|
46
|
+
state: GuardState;
|
|
47
|
+
check(context: RequestContext): void;
|
|
48
|
+
on(eventName: GuardEventName, handler: GuardEventHandler): () => void;
|
|
49
|
+
off(eventName: GuardEventName, handler: GuardEventHandler): void;
|
|
26
50
|
}
|
|
27
51
|
//# sourceMappingURL=GuardFree.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GuardFree.d.ts","sourceRoot":"","sources":["../../src/core/GuardFree.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"GuardFree.d.ts","sourceRoot":"","sources":["../../src/core/GuardFree.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,UAAU,EAAoB,MAAM,gBAAgB,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,cAAc,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE7G;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,kDAAkD;IAClD,EAAE,CAAC,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAAC;IACtE,gCAAgC;IAChC,GAAG,CAAC,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACjE,qDAAqD;IACrD,aAAa,IAAI,UAAU,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,MAAM,aAAa,CAAC,OAAO,SAAS,MAAM,IAAI,OAAO,GAAG,kBAAkB,CAAC;AAEjF;;GAEG;AACH,wBAAgB,KAAK,CAAC,OAAO,SAAS,MAAM,EAC1C,MAAM,EAAE,OAAO,EACf,MAAM,GAAE,WAAgB,EACxB,WAAW,GAAE,UAA+B,GAC3C,aAAa,CAAC,OAAO,CAAC,CAqDxB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,SAAS,SAAS,OAAO,EAAE,EAAE,OAAO,EACrE,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,EAC/B,MAAM,GAAE,WAAgB,GACvB,CAAC,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,GAAG,kBAAkB,CAepD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,GAAE,WAAgB,GAAG,CAAC,GAAG,EAAE,iBAAiB,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,KAAK,IAAI,CAiBrH;AAED;;GAEG;AACH,OAAO,EAAE,UAAU,EAAE,CAAC;AAEtB;;GAEG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjD,UAAU,iBAAiB;IACzB,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,KAAK,CAAC,EAAE,kBAAkB,CAAC;CAC5B;AAED,UAAU,kBAAkB;IAC1B,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;IACrC,EAAE,CAAC,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAAC;IACtE,GAAG,CAAC,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAAC;CAClE"}
|
package/dist/core/GuardFree.js
CHANGED
|
@@ -1,159 +1,114 @@
|
|
|
1
|
+
import { GuardCore, GuardError, createGuardState } from './GuardCore.js';
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Each process thinks it is safe.
|
|
5
|
-
* Single-process protection only.
|
|
6
|
-
*
|
|
7
|
-
* INSTALL: const ai = guard(openai)
|
|
3
|
+
* Wraps an OpenAI-like client with process-local cost, loop, and retry protection.
|
|
8
4
|
*/
|
|
9
|
-
|
|
5
|
+
export function guard(client, config = {}, sharedState = createGuardState()) {
|
|
6
|
+
const core = new GuardCore(config, sharedState);
|
|
7
|
+
const proxies = new WeakMap();
|
|
8
|
+
const wrap = (target, path = []) => {
|
|
9
|
+
const cached = proxies.get(target);
|
|
10
|
+
if (cached)
|
|
11
|
+
return cached;
|
|
12
|
+
const proxy = new Proxy(target, {
|
|
13
|
+
get(currentTarget, prop, receiver) {
|
|
14
|
+
if (prop === 'on')
|
|
15
|
+
return core.on.bind(core);
|
|
16
|
+
if (prop === 'off')
|
|
17
|
+
return core.off.bind(core);
|
|
18
|
+
if (prop === 'getGuardState')
|
|
19
|
+
return core.getState.bind(core);
|
|
20
|
+
const value = Reflect.get(currentTarget, prop, receiver);
|
|
21
|
+
const nextPath = typeof prop === 'string' ? [...path, prop] : path;
|
|
22
|
+
if (typeof value === 'function') {
|
|
23
|
+
return (...args) => {
|
|
24
|
+
const methodPath = nextPath.join('.');
|
|
25
|
+
if (!core.shouldGuardMethod(methodPath)) {
|
|
26
|
+
return Reflect.apply(value, currentTarget, args);
|
|
27
|
+
}
|
|
28
|
+
const context = core.extractContext(args, methodPath);
|
|
29
|
+
core.check(context);
|
|
30
|
+
const result = Reflect.apply(value, currentTarget, stripGuardMetadata(args));
|
|
31
|
+
if (isPromiseLike(result)) {
|
|
32
|
+
return result.then((resolved) => {
|
|
33
|
+
core.recordActualUsage(context, resolved);
|
|
34
|
+
return resolved;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
core.recordActualUsage(context, result);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (isObject(value)) {
|
|
42
|
+
return wrap(value, nextPath);
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
proxies.set(target, proxy);
|
|
48
|
+
return proxy;
|
|
49
|
+
};
|
|
50
|
+
return wrap(client);
|
|
51
|
+
}
|
|
10
52
|
/**
|
|
11
|
-
*
|
|
53
|
+
* Wraps a standalone AI function with the same guard behavior as guard().
|
|
12
54
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* WARNING: Process-local only. Not production safe.
|
|
55
|
+
* The first function argument should be an OpenAI-like request object containing
|
|
56
|
+
* model, messages/prompt/input, and max_tokens/maxOutputTokens when possible.
|
|
17
57
|
*/
|
|
18
|
-
export function
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const state = sharedState || {
|
|
25
|
-
requestCount: 0,
|
|
26
|
-
totalCost: 0,
|
|
27
|
-
lastRequestTime: 0,
|
|
28
|
-
blockedCount: 0,
|
|
29
|
-
};
|
|
30
|
-
return new Proxy(client, {
|
|
31
|
-
get(target, prop) {
|
|
32
|
-
const value = target[prop];
|
|
33
|
-
if (typeof value === 'function') {
|
|
34
|
-
return (...args) => {
|
|
35
|
-
const ctx = extractContext(args, prop);
|
|
36
|
-
// Local budget check - each process thinks it's safe
|
|
37
|
-
if (state.totalCost + ctx.estimatedCost > guardConfig.budget) {
|
|
38
|
-
const saved = guardConfig.budget - state.totalCost;
|
|
39
|
-
state.blockedCount++;
|
|
40
|
-
console.error(`🚨 LOCAL SAFETY: Budget exceeded → saved $${saved.toFixed(2)}`);
|
|
41
|
-
throw new GuardError(`Budget exceeded`, ctx);
|
|
42
|
-
}
|
|
43
|
-
// Local loop detection - basic hash only
|
|
44
|
-
if (detectLocalLoop(ctx.prompt, state)) {
|
|
45
|
-
const saved = ctx.estimatedCost * 20;
|
|
46
|
-
state.blockedCount++;
|
|
47
|
-
console.error(`🚨 LOCAL SAFETY: Loop detected → saved $${saved.toFixed(2)}`);
|
|
48
|
-
throw new GuardError(`Loop detected`, ctx);
|
|
49
|
-
}
|
|
50
|
-
// Local retry detection - basic keyword only
|
|
51
|
-
if (detectLocalRetry(ctx.prompt, state)) {
|
|
52
|
-
const saved = ctx.estimatedCost * 10;
|
|
53
|
-
state.blockedCount++;
|
|
54
|
-
console.error(`🚨 LOCAL SAFETY: Retry detected → saved $${saved.toFixed(2)}`);
|
|
55
|
-
throw new GuardError(`Retry detected`, ctx);
|
|
56
|
-
}
|
|
57
|
-
// Update local state only
|
|
58
|
-
state.requestCount++;
|
|
59
|
-
state.totalCost += ctx.estimatedCost;
|
|
60
|
-
state.lastRequestTime = Date.now();
|
|
61
|
-
return value.apply(target, args);
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
// Handle nested objects (local only)
|
|
65
|
-
if (value && typeof value === 'object') {
|
|
66
|
-
return guard(value, guardConfig, state);
|
|
67
|
-
}
|
|
68
|
-
return value;
|
|
69
|
-
},
|
|
58
|
+
export function guardFunction(fn, config = {}) {
|
|
59
|
+
const methodName = config.guardedMethods?.[0] ?? 'run';
|
|
60
|
+
const container = { [methodName]: fn };
|
|
61
|
+
const guarded = guard(container, {
|
|
62
|
+
...config,
|
|
63
|
+
guardedMethods: [methodName],
|
|
70
64
|
});
|
|
65
|
+
const guardedFn = ((...args) => guarded[methodName](...args));
|
|
66
|
+
guardedFn.on = guarded.on;
|
|
67
|
+
guardedFn.off = guarded.off;
|
|
68
|
+
guardedFn.getGuardState = guarded.getGuardState;
|
|
69
|
+
return guardedFn;
|
|
71
70
|
}
|
|
72
71
|
/**
|
|
73
|
-
* Express middleware
|
|
72
|
+
* Express-compatible middleware that attaches req.localSafety.check() and req.guard.check().
|
|
74
73
|
*/
|
|
75
|
-
export function middleware(config) {
|
|
76
|
-
const
|
|
77
|
-
return (req,
|
|
78
|
-
|
|
79
|
-
state:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
lastRequestTime: 0,
|
|
83
|
-
blockedCount: 0,
|
|
74
|
+
export function middleware(config = {}) {
|
|
75
|
+
const core = new GuardCore(config);
|
|
76
|
+
return (req, _res, next) => {
|
|
77
|
+
const controls = {
|
|
78
|
+
state: core.getState(),
|
|
79
|
+
check: (context) => {
|
|
80
|
+
core.check(context);
|
|
84
81
|
},
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
throw new GuardError('Budget exceeded', ctx);
|
|
88
|
-
}
|
|
89
|
-
if (detectLocalLoop(ctx.prompt, req.localSafety.state)) {
|
|
90
|
-
throw new GuardError('Loop detected', ctx);
|
|
91
|
-
}
|
|
92
|
-
if (detectLocalRetry(ctx.prompt, req.localSafety.state)) {
|
|
93
|
-
throw new GuardError('Retry detected', ctx);
|
|
94
|
-
}
|
|
95
|
-
req.localSafety.state.requestCount++;
|
|
96
|
-
req.localSafety.state.totalCost += ctx.estimatedCost;
|
|
97
|
-
}
|
|
82
|
+
on: core.on.bind(core),
|
|
83
|
+
off: core.off.bind(core),
|
|
98
84
|
};
|
|
85
|
+
req.localSafety = controls;
|
|
86
|
+
req.guard = controls;
|
|
99
87
|
next();
|
|
100
88
|
};
|
|
101
89
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return
|
|
90
|
+
/**
|
|
91
|
+
* Error thrown when a request is blocked before provider execution.
|
|
92
|
+
*/
|
|
93
|
+
export { GuardError };
|
|
94
|
+
/**
|
|
95
|
+
* Pricing lookup re-export kept for compatibility with older imports.
|
|
96
|
+
*/
|
|
97
|
+
export { getPricing } from '../pricing/index.js';
|
|
98
|
+
function isObject(value) {
|
|
99
|
+
return typeof value === 'object' && value !== null;
|
|
112
100
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const retryKeywords = ['retry', 'again', 'repeat', 'error', 'fail', 'timeout'];
|
|
116
|
-
const hasRetryKeyword = retryKeywords.some(keyword => prompt.toLowerCase().includes(keyword));
|
|
117
|
-
if (!hasRetryKeyword)
|
|
118
|
-
return false;
|
|
119
|
-
const recentRetries = state.recentRetries || [];
|
|
120
|
-
const recentRetryCount = recentRetries.filter((r) => r).length;
|
|
121
|
-
recentRetries.push(true);
|
|
122
|
-
if (recentRetries.length > 3)
|
|
123
|
-
recentRetries.shift(); // Very limited memory
|
|
124
|
-
state.recentRetries = recentRetries;
|
|
125
|
-
return recentRetryCount >= 2; // Basic detection only
|
|
101
|
+
function isPromiseLike(value) {
|
|
102
|
+
return isObject(value) && typeof value.then === 'function';
|
|
126
103
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
const inputText = JSON.stringify(messages);
|
|
134
|
-
const estimatedInputTokens = Math.ceil(inputText.length / 4);
|
|
135
|
-
const maxOutputTokens = params.max_tokens || 1000;
|
|
136
|
-
const tokens = estimatedInputTokens + maxOutputTokens;
|
|
137
|
-
const pricing = lookupPricing(model);
|
|
138
|
-
const inputPer1kTokens = pricing?.inputPer1kTokens ?? 0.01;
|
|
139
|
-
const outputPer1kTokens = pricing?.outputPer1kTokens ?? 0.03;
|
|
140
|
-
const estimatedCost = (estimatedInputTokens / 1000) * inputPer1kTokens +
|
|
141
|
-
(maxOutputTokens / 1000) * outputPer1kTokens;
|
|
142
|
-
return {
|
|
143
|
-
model,
|
|
144
|
-
tokens,
|
|
145
|
-
estimatedCost,
|
|
146
|
-
timestamp: Date.now(),
|
|
147
|
-
prompt,
|
|
148
|
-
};
|
|
104
|
+
function stripGuardMetadata(args) {
|
|
105
|
+
const [first, ...rest] = args;
|
|
106
|
+
if (!isPlainRecord(first))
|
|
107
|
+
return args;
|
|
108
|
+
const { projectId: _projectId, project_id: _project_id, userId: _userId, user_id: _user_id, sessionId: _sessionId, session_id: _session_id, ...providerParams } = first;
|
|
109
|
+
return [providerParams, ...rest];
|
|
149
110
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
context;
|
|
153
|
-
constructor(message, context) {
|
|
154
|
-
super(message);
|
|
155
|
-
this.name = 'GuardError';
|
|
156
|
-
this.context = context;
|
|
157
|
-
}
|
|
111
|
+
function isPlainRecord(value) {
|
|
112
|
+
return Object.prototype.toString.call(value) === '[object Object]';
|
|
158
113
|
}
|
|
159
114
|
//# sourceMappingURL=GuardFree.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GuardFree.js","sourceRoot":"","sources":["../../src/core/GuardFree.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"GuardFree.js","sourceRoot":"","sources":["../../src/core/GuardFree.ts"],"names":[],"mappings":"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"}
|
package/dist/core/GuardPro.d.ts
CHANGED
|
@@ -1,27 +1,107 @@
|
|
|
1
|
+
import type { GuardWebhookConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal Redis client surface used by GuardPro. Supplying redisClient is useful for tests.
|
|
4
|
+
*/
|
|
5
|
+
export interface GuardProRedisClient {
|
|
6
|
+
/** Optional connection status exposed by ioredis-compatible clients. */
|
|
7
|
+
status?: string;
|
|
8
|
+
/** Registers a connection event handler. */
|
|
9
|
+
on?(eventName: 'ready' | 'error' | 'close', handler: () => void): unknown;
|
|
10
|
+
/** Opens the Redis connection when the client is lazy. */
|
|
11
|
+
connect?(): Promise<unknown>;
|
|
12
|
+
/** Evaluates the atomic spend increment Lua script. */
|
|
13
|
+
eval(script: string, keys: number, key: string, amount: string, ttlSeconds: string): Promise<unknown>;
|
|
14
|
+
/** Reads the current spend value. */
|
|
15
|
+
get(key: string): Promise<string | null>;
|
|
16
|
+
/** Deletes a spend key. */
|
|
17
|
+
del(key: string): Promise<unknown>;
|
|
18
|
+
/** Closes the connection. */
|
|
19
|
+
quit?(): Promise<unknown>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Configuration for Redis-backed budget enforcement.
|
|
23
|
+
*/
|
|
1
24
|
export interface GuardProConfig {
|
|
25
|
+
/** Redis connection URL. Instances sharing this URL reuse one pooled connection. */
|
|
2
26
|
redisUrl: string;
|
|
27
|
+
/** Budget in USD for each project/session window. */
|
|
3
28
|
budget: number;
|
|
29
|
+
/** Session TTL in seconds. Defaults to 86400. */
|
|
4
30
|
windowSeconds?: number;
|
|
31
|
+
/** Slack webhook URL for budget block notifications. */
|
|
5
32
|
slackWebhook?: string;
|
|
33
|
+
/** Discord webhook URL for budget block notifications. */
|
|
34
|
+
discordWebhook?: string;
|
|
35
|
+
/** Combined webhook configuration. */
|
|
36
|
+
webhooks?: GuardWebhookConfig;
|
|
37
|
+
/** Deprecated compatibility field. GuardPro does not enforce licenses locally. */
|
|
6
38
|
licenseKey?: string;
|
|
39
|
+
/** Optional Redis-compatible client. When omitted, GuardPro pools ioredis clients by URL. */
|
|
40
|
+
redisClient?: GuardProRedisClient;
|
|
7
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Redis-backed budget guard with local fallback when Redis is unavailable.
|
|
44
|
+
*/
|
|
8
45
|
export declare class GuardPro {
|
|
9
|
-
private readonly
|
|
46
|
+
private static readonly pools;
|
|
47
|
+
private readonly redisUrl;
|
|
48
|
+
private readonly redisClient?;
|
|
49
|
+
private readonly poolEntry?;
|
|
10
50
|
private readonly budget;
|
|
11
51
|
private readonly windowSeconds;
|
|
12
|
-
private readonly
|
|
13
|
-
private
|
|
52
|
+
private readonly webhooks?;
|
|
53
|
+
private readonly localSpend;
|
|
54
|
+
private directRedisFailed;
|
|
55
|
+
private directRedisReady;
|
|
56
|
+
/**
|
|
57
|
+
* Creates a GuardPro instance and reuses a pooled Redis connection for the same URL.
|
|
58
|
+
*/
|
|
14
59
|
constructor(config: GuardProConfig);
|
|
60
|
+
/**
|
|
61
|
+
* Atomically charges estimated spend for a project and throws GuardError when budget is exceeded.
|
|
62
|
+
*/
|
|
15
63
|
checkAndCharge(projectId: string, estimatedCost: number): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Returns current spend for a project from Redis when available, otherwise from local fallback state.
|
|
66
|
+
*/
|
|
16
67
|
getSpend(projectId: string): Promise<number>;
|
|
68
|
+
/**
|
|
69
|
+
* Resets spend for a project in Redis when available and always clears local fallback state.
|
|
70
|
+
*/
|
|
17
71
|
resetSpend(projectId: string): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Returns true when the pooled or supplied Redis client is currently connected.
|
|
74
|
+
*/
|
|
18
75
|
isConnected(): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Releases this instance's pooled Redis reference and closes the connection when unused.
|
|
78
|
+
*/
|
|
19
79
|
shutdown(): Promise<void>;
|
|
20
|
-
private
|
|
21
|
-
private
|
|
80
|
+
private static getPoolEntry;
|
|
81
|
+
private attachConnectionEvents;
|
|
82
|
+
private getUsableRedis;
|
|
83
|
+
private connect;
|
|
84
|
+
private incrementRedisOrFallback;
|
|
85
|
+
private incrementRedis;
|
|
86
|
+
private incrementLocal;
|
|
87
|
+
private getLocal;
|
|
88
|
+
private markDisconnected;
|
|
89
|
+
private safeQuit;
|
|
22
90
|
private getSpendKey;
|
|
23
91
|
private createContext;
|
|
24
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Deprecated compatibility helper.
|
|
95
|
+
*
|
|
96
|
+
* This is a format sanity check only. It is not license enforcement and should
|
|
97
|
+
* not be used for commercial access control.
|
|
98
|
+
*/
|
|
25
99
|
export declare function validateLicense(key: string): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Creates GuardPro.
|
|
102
|
+
*
|
|
103
|
+
* The return type remains nullable for backwards compatibility with older
|
|
104
|
+
* callers, but local license rejection has intentionally been removed.
|
|
105
|
+
*/
|
|
26
106
|
export declare function getProGuard(config: GuardProConfig): GuardPro | null;
|
|
27
107
|
//# sourceMappingURL=GuardPro.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GuardPro.d.ts","sourceRoot":"","sources":["../../src/core/GuardPro.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"GuardPro.d.ts","sourceRoot":"","sources":["../../src/core/GuardPro.ts"],"names":[],"mappings":"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"}
|