@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/GuardPro.js
CHANGED
|
@@ -1,135 +1,198 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Usage:
|
|
3
|
-
*
|
|
4
|
-
* import { GuardPro } from '@salimassili/ai-costguard';
|
|
5
|
-
*
|
|
6
|
-
* const guard = new GuardPro({
|
|
7
|
-
* redisUrl: 'redis://localhost:6379',
|
|
8
|
-
* budget: 25,
|
|
9
|
-
* windowSeconds: 86400,
|
|
10
|
-
* slackWebhook: process.env.SLACK_WEBHOOK,
|
|
11
|
-
* licenseKey: process.env.COSTGUARD_LICENSE
|
|
12
|
-
* });
|
|
13
|
-
*
|
|
14
|
-
* await guard.checkAndCharge('production', 0.0042);
|
|
15
|
-
* await guard.shutdown();
|
|
16
|
-
*
|
|
17
|
-
* No Redis? Get a free one at https://upstash.com
|
|
18
|
-
*/
|
|
19
1
|
import { Redis } from 'ioredis';
|
|
20
|
-
import { GuardError } from './
|
|
2
|
+
import { GuardError } from './GuardCore.js';
|
|
3
|
+
import { notifyBlockWebhooks } from './webhooks.js';
|
|
4
|
+
/**
|
|
5
|
+
* Redis-backed budget guard with local fallback when Redis is unavailable.
|
|
6
|
+
*/
|
|
21
7
|
export class GuardPro {
|
|
22
|
-
|
|
8
|
+
static pools = new Map();
|
|
9
|
+
redisUrl;
|
|
10
|
+
redisClient;
|
|
11
|
+
poolEntry;
|
|
23
12
|
budget;
|
|
24
13
|
windowSeconds;
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
webhooks;
|
|
15
|
+
localSpend = new Map();
|
|
16
|
+
directRedisFailed = false;
|
|
17
|
+
directRedisReady = false;
|
|
18
|
+
/**
|
|
19
|
+
* Creates a GuardPro instance and reuses a pooled Redis connection for the same URL.
|
|
20
|
+
*/
|
|
27
21
|
constructor(config) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
this.redisUrl = config.redisUrl;
|
|
23
|
+
this.budget = config.budget;
|
|
24
|
+
this.windowSeconds = config.windowSeconds ?? 86_400;
|
|
25
|
+
this.webhooks = {
|
|
26
|
+
...config.webhooks,
|
|
27
|
+
slack: config.webhooks?.slack ?? config.slackWebhook,
|
|
28
|
+
discord: config.webhooks?.discord ?? config.discordWebhook,
|
|
29
|
+
};
|
|
30
|
+
if (config.redisClient) {
|
|
31
|
+
this.redisClient = config.redisClient;
|
|
32
|
+
this.attachConnectionEvents(config.redisClient);
|
|
33
|
+
return;
|
|
31
34
|
}
|
|
32
|
-
|
|
33
|
-
this.
|
|
34
|
-
|
|
35
|
-
enableOfflineQueue: false,
|
|
36
|
-
retryStrategy: () => null,
|
|
37
|
-
});
|
|
38
|
-
this.redis.on('ready', () => {
|
|
39
|
-
this.connected = true;
|
|
40
|
-
});
|
|
41
|
-
this.redis.on('error', () => {
|
|
42
|
-
this.connected = false;
|
|
43
|
-
});
|
|
44
|
-
this.redis.on('close', () => {
|
|
45
|
-
this.connected = false;
|
|
46
|
-
});
|
|
47
|
-
this.redis.connect().then(() => {
|
|
48
|
-
this.connected = true;
|
|
49
|
-
}).catch(() => {
|
|
50
|
-
throw new GuardError('GuardPro requires a Redis connection. ' +
|
|
51
|
-
'Pass a valid redisUrl in GuardProConfig. ' +
|
|
52
|
-
'Free option: create an Upstash account at https://upstash.com ' +
|
|
53
|
-
'and paste your Redis URL.');
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
if (error instanceof GuardError)
|
|
58
|
-
throw error;
|
|
59
|
-
throw new GuardError('GuardPro failed to initialize Redis. ' +
|
|
60
|
-
'Pass a valid redisUrl in GuardProConfig. ' +
|
|
61
|
-
'Free option: create an Upstash account at https://upstash.com ' +
|
|
62
|
-
'and paste your Redis URL.');
|
|
35
|
+
if (config.redisUrl.trim()) {
|
|
36
|
+
this.poolEntry = GuardPro.getPoolEntry(config.redisUrl);
|
|
37
|
+
this.redisClient = this.poolEntry.client;
|
|
63
38
|
}
|
|
64
|
-
this.budget = config.budget;
|
|
65
|
-
this.windowSeconds = config.windowSeconds ?? 86400;
|
|
66
|
-
this.slackWebhook = config.slackWebhook;
|
|
67
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Atomically charges estimated spend for a project and throws GuardError when budget is exceeded.
|
|
42
|
+
*/
|
|
68
43
|
async checkAndCharge(projectId, estimatedCost) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
const key = this.getSpendKey(projectId);
|
|
76
|
-
total = await this.incrementSpend(key, estimatedCost);
|
|
77
|
-
}
|
|
78
|
-
catch (error) {
|
|
79
|
-
if (error instanceof GuardError)
|
|
80
|
-
throw error;
|
|
81
|
-
throw new GuardError('GuardPro lost Redis connection. ' +
|
|
82
|
-
'Spending is blocked until connection is restored.');
|
|
83
|
-
}
|
|
44
|
+
const key = this.getSpendKey(projectId);
|
|
45
|
+
const redis = await this.getUsableRedis();
|
|
46
|
+
const total = redis
|
|
47
|
+
? await this.incrementRedisOrFallback(redis, key, projectId, estimatedCost)
|
|
48
|
+
: this.incrementLocal(projectId, estimatedCost);
|
|
84
49
|
if (total > this.budget) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
console.error('[CostGuard] Slack webhook failed — budget still enforced.');
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
throw new GuardError(`Project "${projectId}" exceeded budget. ` +
|
|
94
|
-
`Spend: $${total.toFixed(6)} / Budget: $${this.budget.toFixed(6)}`, this.createContext(projectId, estimatedCost));
|
|
50
|
+
const context = this.createContext(projectId, estimatedCost);
|
|
51
|
+
const reason = `Project "${projectId}" exceeded budget. ` +
|
|
52
|
+
`Spend: $${total.toFixed(6)} / Budget: $${this.budget.toFixed(6)}`;
|
|
53
|
+
await notifyBlockWebhooks(this.webhooks, { reason, context });
|
|
54
|
+
throw new GuardError(reason, context, 'BUDGET_EXCEEDED');
|
|
95
55
|
}
|
|
96
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Returns current spend for a project from Redis when available, otherwise from local fallback state.
|
|
59
|
+
*/
|
|
97
60
|
async getSpend(projectId) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
61
|
+
const redis = await this.getUsableRedis();
|
|
62
|
+
if (redis) {
|
|
63
|
+
try {
|
|
64
|
+
const value = await redis.get(this.getSpendKey(projectId));
|
|
65
|
+
return value ? Number(value) : 0;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
this.markDisconnected();
|
|
69
|
+
}
|
|
106
70
|
}
|
|
71
|
+
return this.getLocal(projectId).total;
|
|
107
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Resets spend for a project in Redis when available and always clears local fallback state.
|
|
75
|
+
*/
|
|
108
76
|
async resetSpend(projectId) {
|
|
109
|
-
|
|
77
|
+
this.localSpend.delete(projectId);
|
|
78
|
+
const redis = await this.getUsableRedis();
|
|
79
|
+
if (!redis)
|
|
110
80
|
return;
|
|
111
81
|
try {
|
|
112
|
-
await
|
|
82
|
+
await redis.del(this.getSpendKey(projectId));
|
|
113
83
|
}
|
|
114
84
|
catch {
|
|
115
|
-
|
|
85
|
+
this.markDisconnected();
|
|
116
86
|
}
|
|
117
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Returns true when the pooled or supplied Redis client is currently connected.
|
|
90
|
+
*/
|
|
118
91
|
isConnected() {
|
|
119
|
-
|
|
92
|
+
if (this.poolEntry)
|
|
93
|
+
return this.poolEntry.connected;
|
|
94
|
+
return !this.directRedisFailed && (this.redisClient?.status === 'ready' || this.directRedisReady);
|
|
120
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Releases this instance's pooled Redis reference and closes the connection when unused.
|
|
98
|
+
*/
|
|
121
99
|
async shutdown() {
|
|
100
|
+
if (this.poolEntry) {
|
|
101
|
+
this.poolEntry.refs -= 1;
|
|
102
|
+
if (this.poolEntry.refs <= 0) {
|
|
103
|
+
GuardPro.pools.delete(this.redisUrl);
|
|
104
|
+
await this.safeQuit(this.poolEntry.client);
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (this.redisClient) {
|
|
109
|
+
await this.safeQuit(this.redisClient);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
static getPoolEntry(redisUrl) {
|
|
113
|
+
const existing = GuardPro.pools.get(redisUrl);
|
|
114
|
+
if (existing) {
|
|
115
|
+
existing.refs += 1;
|
|
116
|
+
return existing;
|
|
117
|
+
}
|
|
118
|
+
const entry = {
|
|
119
|
+
client: new Redis(redisUrl, {
|
|
120
|
+
lazyConnect: true,
|
|
121
|
+
enableOfflineQueue: false,
|
|
122
|
+
retryStrategy: () => null,
|
|
123
|
+
}),
|
|
124
|
+
refs: 1,
|
|
125
|
+
connected: false,
|
|
126
|
+
};
|
|
127
|
+
entry.client.on?.('ready', () => {
|
|
128
|
+
entry.connected = true;
|
|
129
|
+
});
|
|
130
|
+
entry.client.on?.('error', () => {
|
|
131
|
+
entry.connected = false;
|
|
132
|
+
});
|
|
133
|
+
entry.client.on?.('close', () => {
|
|
134
|
+
entry.connected = false;
|
|
135
|
+
});
|
|
136
|
+
GuardPro.pools.set(redisUrl, entry);
|
|
137
|
+
return entry;
|
|
138
|
+
}
|
|
139
|
+
attachConnectionEvents(client) {
|
|
140
|
+
client.on?.('ready', () => {
|
|
141
|
+
this.directRedisReady = true;
|
|
142
|
+
this.directRedisFailed = false;
|
|
143
|
+
});
|
|
144
|
+
client.on?.('error', () => {
|
|
145
|
+
this.directRedisReady = false;
|
|
146
|
+
this.directRedisFailed = true;
|
|
147
|
+
});
|
|
148
|
+
client.on?.('close', () => {
|
|
149
|
+
this.directRedisReady = false;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
async getUsableRedis() {
|
|
153
|
+
const client = this.redisClient;
|
|
154
|
+
if (!client)
|
|
155
|
+
return null;
|
|
156
|
+
if (!this.poolEntry && this.directRedisFailed)
|
|
157
|
+
return null;
|
|
158
|
+
if (client.status === 'ready')
|
|
159
|
+
return client;
|
|
160
|
+
if (this.poolEntry) {
|
|
161
|
+
if (this.poolEntry.connected)
|
|
162
|
+
return client;
|
|
163
|
+
this.poolEntry.connectPromise ??= this.connect(client);
|
|
164
|
+
return this.poolEntry.connectPromise;
|
|
165
|
+
}
|
|
166
|
+
return this.connect(client);
|
|
167
|
+
}
|
|
168
|
+
async connect(client) {
|
|
122
169
|
try {
|
|
123
|
-
await
|
|
170
|
+
await client.connect?.();
|
|
171
|
+
if (this.poolEntry) {
|
|
172
|
+
this.poolEntry.connected = client.status === undefined || client.status === 'ready';
|
|
173
|
+
this.poolEntry.connectPromise = undefined;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
this.directRedisFailed = false;
|
|
177
|
+
this.directRedisReady = true;
|
|
178
|
+
}
|
|
179
|
+
return client;
|
|
124
180
|
}
|
|
125
181
|
catch {
|
|
126
|
-
|
|
182
|
+
this.markDisconnected();
|
|
183
|
+
return null;
|
|
127
184
|
}
|
|
128
|
-
|
|
129
|
-
|
|
185
|
+
}
|
|
186
|
+
async incrementRedisOrFallback(redis, key, projectId, estimatedCost) {
|
|
187
|
+
try {
|
|
188
|
+
return await this.incrementRedis(redis, key, estimatedCost);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
this.markDisconnected();
|
|
192
|
+
return this.incrementLocal(projectId, estimatedCost);
|
|
130
193
|
}
|
|
131
194
|
}
|
|
132
|
-
async
|
|
195
|
+
async incrementRedis(redis, key, estimatedCost) {
|
|
133
196
|
const script = `
|
|
134
197
|
local total = redis.call("INCRBYFLOAT", KEYS[1], ARGV[1])
|
|
135
198
|
local ttl = redis.call("TTL", KEYS[1])
|
|
@@ -138,21 +201,44 @@ export class GuardPro {
|
|
|
138
201
|
end
|
|
139
202
|
return total
|
|
140
203
|
`;
|
|
141
|
-
const total = await
|
|
204
|
+
const total = await redis.eval(script, 1, key, estimatedCost.toString(), this.windowSeconds.toString());
|
|
142
205
|
return Number(total);
|
|
143
206
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
207
|
+
incrementLocal(projectId, estimatedCost) {
|
|
208
|
+
const record = this.getLocal(projectId);
|
|
209
|
+
record.total += estimatedCost;
|
|
210
|
+
this.localSpend.set(projectId, record);
|
|
211
|
+
return record.total;
|
|
212
|
+
}
|
|
213
|
+
getLocal(projectId) {
|
|
214
|
+
const now = Date.now();
|
|
215
|
+
const existing = this.localSpend.get(projectId);
|
|
216
|
+
if (existing && existing.expiresAt > now) {
|
|
217
|
+
return existing;
|
|
218
|
+
}
|
|
219
|
+
const fresh = {
|
|
220
|
+
total: 0,
|
|
221
|
+
expiresAt: now + this.windowSeconds * 1000,
|
|
222
|
+
};
|
|
223
|
+
this.localSpend.set(projectId, fresh);
|
|
224
|
+
return fresh;
|
|
225
|
+
}
|
|
226
|
+
markDisconnected() {
|
|
227
|
+
if (this.poolEntry) {
|
|
228
|
+
this.poolEntry.connected = false;
|
|
229
|
+
this.poolEntry.connectPromise = undefined;
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
this.directRedisFailed = true;
|
|
233
|
+
this.directRedisReady = false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
async safeQuit(client) {
|
|
237
|
+
try {
|
|
238
|
+
await client.quit?.();
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// Best-effort shutdown only.
|
|
156
242
|
}
|
|
157
243
|
}
|
|
158
244
|
getSpendKey(projectId) {
|
|
@@ -162,21 +248,30 @@ export class GuardPro {
|
|
|
162
248
|
return {
|
|
163
249
|
model: 'unknown',
|
|
164
250
|
tokens: 0,
|
|
251
|
+
inputTokens: 0,
|
|
252
|
+
outputTokens: 0,
|
|
165
253
|
estimatedCost,
|
|
166
254
|
timestamp: Date.now(),
|
|
167
|
-
prompt: `project:${projectId}
|
|
255
|
+
prompt: `project:${projectId}`,
|
|
168
256
|
};
|
|
169
257
|
}
|
|
170
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Deprecated compatibility helper.
|
|
261
|
+
*
|
|
262
|
+
* This is a format sanity check only. It is not license enforcement and should
|
|
263
|
+
* not be used for commercial access control.
|
|
264
|
+
*/
|
|
171
265
|
export function validateLicense(key) {
|
|
172
|
-
|
|
173
|
-
return false;
|
|
174
|
-
const checksum = Array.from(key).reduce((sum, char) => sum + char.charCodeAt(0), 0);
|
|
175
|
-
return checksum % 7 === 0;
|
|
266
|
+
return typeof key === 'string' && key.trim().length >= 16;
|
|
176
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Creates GuardPro.
|
|
270
|
+
*
|
|
271
|
+
* The return type remains nullable for backwards compatibility with older
|
|
272
|
+
* callers, but local license rejection has intentionally been removed.
|
|
273
|
+
*/
|
|
177
274
|
export function getProGuard(config) {
|
|
178
|
-
if (config.licenseKey && !validateLicense(config.licenseKey))
|
|
179
|
-
return null;
|
|
180
275
|
return new GuardPro(config);
|
|
181
276
|
}
|
|
182
277
|
//# sourceMappingURL=GuardPro.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GuardPro.js","sourceRoot":"","sources":["../../src/core/GuardPro.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"GuardPro.js","sourceRoot":"","sources":["../../src/core/GuardPro.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { GuardEvent } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Prompt retention mode for JSONL event logs.
|
|
4
|
+
*/
|
|
5
|
+
export type EventLogPromptMode = 'none' | 'preview';
|
|
6
|
+
/**
|
|
7
|
+
* Stable JSONL record written for local dashboards and offline analysis.
|
|
8
|
+
*/
|
|
9
|
+
export interface GuardEventLogRecord {
|
|
10
|
+
version: 1;
|
|
11
|
+
timestamp: string;
|
|
12
|
+
type: GuardEvent['type'];
|
|
13
|
+
code?: GuardEvent['code'];
|
|
14
|
+
reason?: string;
|
|
15
|
+
model: string;
|
|
16
|
+
method?: string;
|
|
17
|
+
scopeKey: string;
|
|
18
|
+
estimatedCost: number;
|
|
19
|
+
actualCost?: number;
|
|
20
|
+
inputTokens?: number;
|
|
21
|
+
outputTokens?: number;
|
|
22
|
+
tokens: number;
|
|
23
|
+
promptPreview?: string;
|
|
24
|
+
state: {
|
|
25
|
+
requestCount: number;
|
|
26
|
+
blockedCount: number;
|
|
27
|
+
totalCost: number;
|
|
28
|
+
attemptedCost: number;
|
|
29
|
+
blockedCost: number;
|
|
30
|
+
actualCost: number;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Appends a redacted guard event to a local JSONL file.
|
|
35
|
+
*/
|
|
36
|
+
export declare function appendGuardEventLog(eventLogPath: string | undefined, event: GuardEvent, promptMode?: EventLogPromptMode): void;
|
|
37
|
+
//# sourceMappingURL=event-log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-log.d.ts","sourceRoot":"","sources":["../../src/core/event-log.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,SAAS,CAAC;AAEpD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,CAAC,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IACzB,IAAI,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE;QACL,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,KAAK,EAAE,UAAU,EACjB,UAAU,GAAE,kBAA2B,GACtC,IAAI,CASN"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { mkdirSync, appendFileSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Appends a redacted guard event to a local JSONL file.
|
|
5
|
+
*/
|
|
6
|
+
export function appendGuardEventLog(eventLogPath, event, promptMode = 'none') {
|
|
7
|
+
if (!eventLogPath)
|
|
8
|
+
return;
|
|
9
|
+
try {
|
|
10
|
+
mkdirSync(dirname(eventLogPath), { recursive: true });
|
|
11
|
+
appendFileSync(eventLogPath, JSON.stringify(toEventLogRecord(event, promptMode)) + '\n', 'utf8');
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// Event logs are local observability only and must not affect guard decisions.
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function toEventLogRecord(event, promptMode) {
|
|
18
|
+
const record = {
|
|
19
|
+
version: 1,
|
|
20
|
+
timestamp: new Date(event.context.timestamp).toISOString(),
|
|
21
|
+
type: event.type,
|
|
22
|
+
code: event.code,
|
|
23
|
+
reason: event.reason,
|
|
24
|
+
model: event.context.model,
|
|
25
|
+
method: event.context.method,
|
|
26
|
+
scopeKey: event.context.scopeKey ?? 'default',
|
|
27
|
+
estimatedCost: roundMoney(event.context.estimatedCost),
|
|
28
|
+
actualCost: event.context.actualCost === undefined ? undefined : roundMoney(event.context.actualCost),
|
|
29
|
+
inputTokens: event.context.inputTokens,
|
|
30
|
+
outputTokens: event.context.outputTokens,
|
|
31
|
+
tokens: event.context.tokens,
|
|
32
|
+
state: {
|
|
33
|
+
requestCount: event.state.requestCount,
|
|
34
|
+
blockedCount: event.state.blockedCount,
|
|
35
|
+
totalCost: roundMoney(event.state.totalCost),
|
|
36
|
+
attemptedCost: roundMoney(event.state.attemptedCost),
|
|
37
|
+
blockedCost: roundMoney(event.state.blockedCost),
|
|
38
|
+
actualCost: roundMoney(event.state.actualCost),
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
if (promptMode === 'preview' && event.context.prompt.trim()) {
|
|
42
|
+
record.promptPreview = event.context.prompt.slice(0, 160);
|
|
43
|
+
}
|
|
44
|
+
return record;
|
|
45
|
+
}
|
|
46
|
+
function roundMoney(value) {
|
|
47
|
+
return Math.round(value * 1_000_000) / 1_000_000;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=event-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-log.js","sourceRoot":"","sources":["../../src/core/event-log.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoCpC;;GAEG;AACH,MAAM,UAAU,mBAAmB,CACjC,YAAgC,EAChC,KAAiB,EACjB,aAAiC,MAAM;IAEvC,IAAI,CAAC,YAAY;QAAE,OAAO;IAE1B,IAAI,CAAC;QACH,SAAS,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,cAAc,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;IACnG,CAAC;IAAC,MAAM,CAAC;QACP,+EAA+E;IACjF,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAiB,EAAE,UAA8B;IACzE,MAAM,MAAM,GAAwB;QAClC,OAAO,EAAE,CAAC;QACV,SAAS,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE;QAC1D,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK;QAC1B,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM;QAC5B,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ,IAAI,SAAS;QAC7C,aAAa,EAAE,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC;QACtD,UAAU,EAAE,KAAK,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC;QACrG,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,WAAW;QACtC,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,YAAY;QACxC,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM;QAC5B,KAAK,EAAE;YACL,YAAY,EAAE,KAAK,CAAC,KAAK,CAAC,YAAY;YACtC,YAAY,EAAE,KAAK,CAAC,KAAK,CAAC,YAAY;YACtC,SAAS,EAAE,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC;YAC5C,aAAa,EAAE,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC;YACpD,WAAW,EAAE,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC;YAChD,UAAU,EAAE,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC;SAC/C;KACF,CAAC;IAEF,IAAI,UAAU,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;QAC5D,MAAM,CAAC,aAAa,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC;AACnD,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { GuardEvent, GuardEventHandler, GuardEventName } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Small synchronous event emitter used by guarded clients.
|
|
4
|
+
*/
|
|
5
|
+
export declare class GuardEventEmitter {
|
|
6
|
+
private readonly handlers;
|
|
7
|
+
/**
|
|
8
|
+
* Subscribes to a guard event and returns an unsubscribe function.
|
|
9
|
+
*/
|
|
10
|
+
on(eventName: GuardEventName, handler: GuardEventHandler): () => void;
|
|
11
|
+
/**
|
|
12
|
+
* Removes one event handler from a guard event.
|
|
13
|
+
*/
|
|
14
|
+
off(eventName: GuardEventName, handler: GuardEventHandler): void;
|
|
15
|
+
/**
|
|
16
|
+
* Emits an event to all current subscribers.
|
|
17
|
+
*/
|
|
18
|
+
emit(event: GuardEvent): void;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/core/events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEhF;;GAEG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqD;IAE9E;;OAEG;IACH,EAAE,CAAC,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,IAAI;IAUrE;;OAEG;IACH,GAAG,CAAC,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,iBAAiB,GAAG,IAAI;IAUhE;;OAEG;IACH,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;CAY9B"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small synchronous event emitter used by guarded clients.
|
|
3
|
+
*/
|
|
4
|
+
export class GuardEventEmitter {
|
|
5
|
+
handlers = new Map();
|
|
6
|
+
/**
|
|
7
|
+
* Subscribes to a guard event and returns an unsubscribe function.
|
|
8
|
+
*/
|
|
9
|
+
on(eventName, handler) {
|
|
10
|
+
const handlers = this.handlers.get(eventName) ?? new Set();
|
|
11
|
+
handlers.add(handler);
|
|
12
|
+
this.handlers.set(eventName, handlers);
|
|
13
|
+
return () => {
|
|
14
|
+
this.off(eventName, handler);
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Removes one event handler from a guard event.
|
|
19
|
+
*/
|
|
20
|
+
off(eventName, handler) {
|
|
21
|
+
const handlers = this.handlers.get(eventName);
|
|
22
|
+
if (!handlers)
|
|
23
|
+
return;
|
|
24
|
+
handlers.delete(handler);
|
|
25
|
+
if (handlers.size === 0) {
|
|
26
|
+
this.handlers.delete(eventName);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Emits an event to all current subscribers.
|
|
31
|
+
*/
|
|
32
|
+
emit(event) {
|
|
33
|
+
const handlers = this.handlers.get(event.type);
|
|
34
|
+
if (!handlers)
|
|
35
|
+
return;
|
|
36
|
+
for (const handler of handlers) {
|
|
37
|
+
try {
|
|
38
|
+
handler(event);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Event handlers are opt-in observability hooks and must not affect the guard decision.
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/core/events.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,OAAO,iBAAiB;IACX,QAAQ,GAAG,IAAI,GAAG,EAA0C,CAAC;IAE9E;;OAEG;IACH,EAAE,CAAC,SAAyB,EAAE,OAA0B;QACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,IAAI,GAAG,EAAqB,CAAC;QAC9E,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACtB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAEvC,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAC/B,CAAC,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,GAAG,CAAC,SAAyB,EAAE,OAA0B;QACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC9C,IAAI,CAAC,QAAQ;YAAE,OAAO;QAEtB,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACzB,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACxB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED;;OAEG;IACH,IAAI,CAAC,KAAiB;QACpB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/C,IAAI,CAAC,QAAQ;YAAE,OAAO;QAEtB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,IAAI,CAAC;gBACH,OAAO,CAAC,KAAK,CAAC,CAAC;YACjB,CAAC;YAAC,MAAM,CAAC;gBACP,wFAAwF;YAC1F,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds a character trigram frequency vector for a prompt.
|
|
3
|
+
*/
|
|
4
|
+
export declare function characterTrigrams(input: string): Map<string, number>;
|
|
5
|
+
/**
|
|
6
|
+
* Calculates cosine similarity between two strings using character trigrams.
|
|
7
|
+
*/
|
|
8
|
+
export declare function cosineSimilarity(left: string, right: string): number;
|
|
9
|
+
/**
|
|
10
|
+
* Returns the highest trigram cosine similarity between a prompt and a prompt history.
|
|
11
|
+
*/
|
|
12
|
+
export declare function maxCosineSimilarity(prompt: string, history: readonly string[]): number;
|
|
13
|
+
//# sourceMappingURL=similarity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"similarity.d.ts","sourceRoot":"","sources":["../../src/core/similarity.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAgBpE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAqBpE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAEtF"}
|