@salimassili/ai-costguard 1.2.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 +62 -0
- package/LICENSE +21 -0
- package/README.md +415 -177
- package/benchmarks/run.mjs +229 -0
- package/benchmarks/token-accuracy.mjs +86 -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 +4 -5
- package/dist/core/CostGuard.d.ts.map +1 -1
- package/dist/core/CostGuard.js +2 -3
- 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 +76 -8
- package/dist/core/GuardPro.d.ts.map +1 -1
- package/dist/core/GuardPro.js +213 -130
- 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 +151 -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 +4 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/pricing/index.d.ts +26 -2
- package/dist/pricing/index.d.ts.map +1 -1
- package/dist/pricing/index.js +100 -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 +70 -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 +76 -46
package/dist/core/GuardPro.js
CHANGED
|
@@ -1,158 +1,244 @@
|
|
|
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;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async incrementRedisOrFallback(redis, key, projectId, estimatedCost) {
|
|
187
|
+
try {
|
|
188
|
+
return await this.incrementRedis(redis, key, estimatedCost);
|
|
127
189
|
}
|
|
128
|
-
|
|
129
|
-
this.
|
|
190
|
+
catch {
|
|
191
|
+
this.markDisconnected();
|
|
192
|
+
return this.incrementLocal(projectId, estimatedCost);
|
|
130
193
|
}
|
|
131
194
|
}
|
|
132
|
-
async
|
|
133
|
-
const script = `
|
|
134
|
-
local total = redis.call("INCRBYFLOAT", KEYS[1], ARGV[1])
|
|
135
|
-
local ttl = redis.call("TTL", KEYS[1])
|
|
136
|
-
if ttl == -1 then
|
|
137
|
-
redis.call("EXPIRE", KEYS[1], ARGV[2])
|
|
138
|
-
end
|
|
139
|
-
return total
|
|
195
|
+
async incrementRedis(redis, key, estimatedCost) {
|
|
196
|
+
const script = `
|
|
197
|
+
local total = redis.call("INCRBYFLOAT", KEYS[1], ARGV[1])
|
|
198
|
+
local ttl = redis.call("TTL", KEYS[1])
|
|
199
|
+
if ttl == -1 then
|
|
200
|
+
redis.call("EXPIRE", KEYS[1], ARGV[2])
|
|
201
|
+
end
|
|
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,18 @@ 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
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const checksum = Array.from(key).reduce((sum, char) => sum + char.charCodeAt(0), 0);
|
|
175
|
-
return checksum % 7 === 0;
|
|
176
|
-
}
|
|
259
|
+
/**
|
|
260
|
+
* Creates GuardPro.
|
|
261
|
+
*/
|
|
177
262
|
export function getProGuard(config) {
|
|
178
|
-
if (config.licenseKey && !validateLicense(config.licenseKey))
|
|
179
|
-
return null;
|
|
180
263
|
return new GuardPro(config);
|
|
181
264
|
}
|
|
182
265
|
//# 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"}
|