rax-flow-core 0.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/dist/governance/audit-trail.d.ts +94 -0
- package/dist/governance/audit-trail.d.ts.map +1 -0
- package/dist/governance/audit-trail.js +246 -0
- package/dist/governance/audit-trail.js.map +1 -0
- package/dist/governance/policy-engine.d.ts +101 -0
- package/dist/governance/policy-engine.d.ts.map +1 -0
- package/dist/governance/policy-engine.js +446 -0
- package/dist/governance/policy-engine.js.map +1 -0
- package/dist/governance/rbac-engine.d.ts +59 -0
- package/dist/governance/rbac-engine.d.ts.map +1 -0
- package/dist/governance/rbac-engine.js +183 -0
- package/dist/governance/rbac-engine.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/memory/embeddings-service.d.ts +116 -0
- package/dist/memory/embeddings-service.d.ts.map +1 -0
- package/dist/memory/embeddings-service.js +287 -0
- package/dist/memory/embeddings-service.js.map +1 -0
- package/dist/memory/local-vector-store.d.ts +37 -3
- package/dist/memory/local-vector-store.d.ts.map +1 -1
- package/dist/memory/local-vector-store.js +91 -8
- package/dist/memory/local-vector-store.js.map +1 -1
- package/dist/orchestrator/core-orchestrator.d.ts +12 -0
- package/dist/orchestrator/core-orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/core-orchestrator.js +75 -0
- package/dist/orchestrator/core-orchestrator.js.map +1 -1
- package/dist/orchestrator/task-decomposer.d.ts +56 -0
- package/dist/orchestrator/task-decomposer.d.ts.map +1 -0
- package/dist/orchestrator/task-decomposer.js +286 -0
- package/dist/orchestrator/task-decomposer.js.map +1 -0
- package/dist/plugins/plugin-system.d.ts +84 -1
- package/dist/plugins/plugin-system.d.ts.map +1 -1
- package/dist/plugins/plugin-system.js +91 -0
- package/dist/plugins/plugin-system.js.map +1 -1
- package/package.json +1 -1
- package/src/governance/audit-trail.ts +375 -0
- package/src/governance/policy-engine.ts +582 -0
- package/src/governance/rbac-engine.ts +244 -0
- package/src/index.ts +5 -2
- package/src/memory/embeddings-service.ts +322 -0
- package/src/memory/local-vector-store.ts +105 -8
- package/src/orchestrator/core-orchestrator.ts +78 -0
- package/src/orchestrator/task-decomposer.ts +428 -0
- package/src/plugins/plugin-system.ts +162 -1
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
import { Policy } from "../plugins/governance-plugin.js";
|
|
2
|
+
import { PolicyViolation, PolicyDecision, AgentInput, AgentOutput } from "../types/contracts.js";
|
|
3
|
+
|
|
4
|
+
export interface GovernanceRule {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
severity: "low" | "medium" | "high" | "critical";
|
|
10
|
+
scope: "input" | "output" | "both";
|
|
11
|
+
evaluate: (context: GovernanceContext) => Promise<PolicyDecision>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface GovernanceContext {
|
|
15
|
+
taskId: string;
|
|
16
|
+
nodeId?: string;
|
|
17
|
+
agent?: string;
|
|
18
|
+
input?: AgentInput;
|
|
19
|
+
output?: AgentOutput;
|
|
20
|
+
metadata?: Record<string, unknown>;
|
|
21
|
+
costUsd?: number;
|
|
22
|
+
tokensUsed?: number;
|
|
23
|
+
latencyMs?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface GovernanceConfig {
|
|
27
|
+
maxCostPerRun: number;
|
|
28
|
+
maxTokensPerRun: number;
|
|
29
|
+
maxLatencyMs: number;
|
|
30
|
+
allowedIntents?: string[];
|
|
31
|
+
blockedPatterns?: string[];
|
|
32
|
+
requireApprovalFor?: string[];
|
|
33
|
+
rateLimitPerMinute?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface GovernanceStats {
|
|
37
|
+
totalEvaluations: number;
|
|
38
|
+
violationsByType: Record<string, number>;
|
|
39
|
+
violationsBySeverity: Record<string, number>;
|
|
40
|
+
blockedCount: number;
|
|
41
|
+
allowedCount: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const DEFAULT_CONFIG: GovernanceConfig = {
|
|
45
|
+
maxCostPerRun: 1.0,
|
|
46
|
+
maxTokensPerRun: 100000,
|
|
47
|
+
maxLatencyMs: 60000
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export class GovernanceEngine {
|
|
51
|
+
private rules: Map<string, GovernanceRule> = new Map();
|
|
52
|
+
private config: GovernanceConfig;
|
|
53
|
+
private stats: GovernanceStats = {
|
|
54
|
+
totalEvaluations: 0,
|
|
55
|
+
violationsByType: {},
|
|
56
|
+
violationsBySeverity: {},
|
|
57
|
+
blockedCount: 0,
|
|
58
|
+
allowedCount: 0
|
|
59
|
+
};
|
|
60
|
+
private rateLimitTracker: Map<string, number[]> = new Map();
|
|
61
|
+
|
|
62
|
+
constructor(config: Partial<GovernanceConfig> = {}) {
|
|
63
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
64
|
+
this.initializeDefaultRules();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private initializeDefaultRules(): void {
|
|
68
|
+
this.addRule({
|
|
69
|
+
id: "cost_limit",
|
|
70
|
+
name: "Cost Limit",
|
|
71
|
+
description: "Ensures execution cost stays within limits",
|
|
72
|
+
enabled: true,
|
|
73
|
+
severity: "high",
|
|
74
|
+
scope: "output",
|
|
75
|
+
evaluate: async (ctx) => this.evaluateCostLimit(ctx)
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
this.addRule({
|
|
79
|
+
id: "token_limit",
|
|
80
|
+
name: "Token Limit",
|
|
81
|
+
description: "Ensures token usage stays within limits",
|
|
82
|
+
enabled: true,
|
|
83
|
+
severity: "medium",
|
|
84
|
+
scope: "output",
|
|
85
|
+
evaluate: async (ctx) => this.evaluateTokenLimit(ctx)
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
this.addRule({
|
|
89
|
+
id: "latency_limit",
|
|
90
|
+
name: "Latency Limit",
|
|
91
|
+
description: "Ensures execution time stays within limits",
|
|
92
|
+
enabled: true,
|
|
93
|
+
severity: "medium",
|
|
94
|
+
scope: "output",
|
|
95
|
+
evaluate: async (ctx) => this.evaluateLatencyLimit(ctx)
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
this.addRule({
|
|
99
|
+
id: "blocked_content",
|
|
100
|
+
name: "Blocked Content",
|
|
101
|
+
description: "Blocks prompts containing disallowed patterns",
|
|
102
|
+
enabled: true,
|
|
103
|
+
severity: "critical",
|
|
104
|
+
scope: "input",
|
|
105
|
+
evaluate: async (ctx) => this.evaluateBlockedContent(ctx)
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
this.addRule({
|
|
109
|
+
id: "intent_whitelist",
|
|
110
|
+
name: "Intent Whitelist",
|
|
111
|
+
description: "Only allows specified intents",
|
|
112
|
+
enabled: false,
|
|
113
|
+
severity: "high",
|
|
114
|
+
scope: "input",
|
|
115
|
+
evaluate: async (ctx) => this.evaluateIntentWhitelist(ctx)
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
this.addRule({
|
|
119
|
+
id: "approval_required",
|
|
120
|
+
name: "Approval Required",
|
|
121
|
+
description: "Requires human approval for certain operations",
|
|
122
|
+
enabled: true,
|
|
123
|
+
severity: "medium",
|
|
124
|
+
scope: "both",
|
|
125
|
+
evaluate: async (ctx) => this.evaluateApprovalRequired(ctx)
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
this.addRule({
|
|
129
|
+
id: "rate_limit",
|
|
130
|
+
name: "Rate Limit",
|
|
131
|
+
description: "Limits request frequency",
|
|
132
|
+
enabled: true,
|
|
133
|
+
severity: "high",
|
|
134
|
+
scope: "input",
|
|
135
|
+
evaluate: async (ctx) => this.evaluateRateLimit(ctx)
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
addRule(rule: GovernanceRule): void {
|
|
140
|
+
this.rules.set(rule.id, rule);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
removeRule(ruleId: string): boolean {
|
|
144
|
+
return this.rules.delete(ruleId);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
enableRule(ruleId: string): boolean {
|
|
148
|
+
const rule = this.rules.get(ruleId);
|
|
149
|
+
if (rule) {
|
|
150
|
+
rule.enabled = true;
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
disableRule(ruleId: string): boolean {
|
|
157
|
+
const rule = this.rules.get(ruleId);
|
|
158
|
+
if (rule) {
|
|
159
|
+
rule.enabled = false;
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async evaluateInput(context: GovernanceContext): Promise<PolicyDecision> {
|
|
166
|
+
const allViolations: PolicyViolation[] = [];
|
|
167
|
+
|
|
168
|
+
for (const rule of this.rules.values()) {
|
|
169
|
+
if (!rule.enabled || (rule.scope !== "input" && rule.scope !== "both")) continue;
|
|
170
|
+
|
|
171
|
+
const decision = await rule.evaluate(context);
|
|
172
|
+
this.updateStats(rule.id, decision);
|
|
173
|
+
|
|
174
|
+
if (!decision.allowed) {
|
|
175
|
+
allViolations.push(...decision.violations);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
allowed: allViolations.length === 0 || !allViolations.some(v => v.severity === "critical"),
|
|
181
|
+
violations: allViolations
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async evaluateOutput(context: GovernanceContext): Promise<PolicyDecision> {
|
|
186
|
+
const allViolations: PolicyViolation[] = [];
|
|
187
|
+
|
|
188
|
+
for (const rule of this.rules.values()) {
|
|
189
|
+
if (!rule.enabled || (rule.scope !== "output" && rule.scope !== "both")) continue;
|
|
190
|
+
|
|
191
|
+
const decision = await rule.evaluate(context);
|
|
192
|
+
this.updateStats(rule.id, decision);
|
|
193
|
+
|
|
194
|
+
if (!decision.allowed) {
|
|
195
|
+
allViolations.push(...decision.violations);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
allowed: !allViolations.some(v => v.severity === "critical" || v.severity === "high"),
|
|
201
|
+
violations: allViolations
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async evaluate(context: GovernanceContext, phase: "input" | "output"): Promise<PolicyDecision> {
|
|
206
|
+
this.stats.totalEvaluations++;
|
|
207
|
+
|
|
208
|
+
if (phase === "input") {
|
|
209
|
+
return this.evaluateInput(context);
|
|
210
|
+
}
|
|
211
|
+
return this.evaluateOutput(context);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private async evaluateCostLimit(context: GovernanceContext): Promise<PolicyDecision> {
|
|
215
|
+
const cost = context.costUsd ?? context.output?.costUsd ?? 0;
|
|
216
|
+
|
|
217
|
+
if (cost > this.config.maxCostPerRun) {
|
|
218
|
+
return {
|
|
219
|
+
allowed: false,
|
|
220
|
+
violations: [{
|
|
221
|
+
policy: "cost_limit",
|
|
222
|
+
severity: "high",
|
|
223
|
+
message: `Cost $${cost.toFixed(4)} exceeds limit $${this.config.maxCostPerRun}`
|
|
224
|
+
}]
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (cost > this.config.maxCostPerRun * 0.8) {
|
|
229
|
+
return {
|
|
230
|
+
allowed: true,
|
|
231
|
+
violations: [{
|
|
232
|
+
policy: "cost_limit",
|
|
233
|
+
severity: "low",
|
|
234
|
+
message: `Cost approaching limit: $${cost.toFixed(4)} / $${this.config.maxCostPerRun}`
|
|
235
|
+
}]
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return { allowed: true, violations: [] };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private async evaluateTokenLimit(context: GovernanceContext): Promise<PolicyDecision> {
|
|
243
|
+
const tokens = context.tokensUsed ?? context.output?.usage?.totalTokens ?? 0;
|
|
244
|
+
|
|
245
|
+
if (tokens > this.config.maxTokensPerRun) {
|
|
246
|
+
return {
|
|
247
|
+
allowed: false,
|
|
248
|
+
violations: [{
|
|
249
|
+
policy: "token_limit",
|
|
250
|
+
severity: "medium",
|
|
251
|
+
message: `Token usage ${tokens} exceeds limit ${this.config.maxTokensPerRun}`
|
|
252
|
+
}]
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return { allowed: true, violations: [] };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private async evaluateLatencyLimit(context: GovernanceContext): Promise<PolicyDecision> {
|
|
260
|
+
const latency = context.latencyMs ?? 0;
|
|
261
|
+
|
|
262
|
+
if (latency > this.config.maxLatencyMs) {
|
|
263
|
+
return {
|
|
264
|
+
allowed: false,
|
|
265
|
+
violations: [{
|
|
266
|
+
policy: "latency_limit",
|
|
267
|
+
severity: "medium",
|
|
268
|
+
message: `Latency ${latency}ms exceeds limit ${this.config.maxLatencyMs}ms`
|
|
269
|
+
}]
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return { allowed: true, violations: [] };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
private async evaluateBlockedContent(context: GovernanceContext): Promise<PolicyDecision> {
|
|
277
|
+
const violations: PolicyViolation[] = [];
|
|
278
|
+
const patterns = this.config.blockedPatterns ?? [];
|
|
279
|
+
|
|
280
|
+
if (!context.input?.userPrompt) {
|
|
281
|
+
return { allowed: true, violations: [] };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const text = context.input.userPrompt.toLowerCase();
|
|
285
|
+
|
|
286
|
+
for (const pattern of patterns) {
|
|
287
|
+
if (text.includes(pattern.toLowerCase())) {
|
|
288
|
+
violations.push({
|
|
289
|
+
policy: "blocked_content",
|
|
290
|
+
severity: "critical",
|
|
291
|
+
message: `Prompt contains blocked pattern: ${pattern}`
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const dangerousPatterns = [
|
|
297
|
+
"ignore previous instructions",
|
|
298
|
+
"disregard all",
|
|
299
|
+
"override safety",
|
|
300
|
+
"bypass restrictions"
|
|
301
|
+
];
|
|
302
|
+
|
|
303
|
+
for (const pattern of dangerousPatterns) {
|
|
304
|
+
if (text.includes(pattern)) {
|
|
305
|
+
violations.push({
|
|
306
|
+
policy: "blocked_content",
|
|
307
|
+
severity: "critical",
|
|
308
|
+
message: `Potential prompt injection detected: ${pattern}`
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
allowed: violations.length === 0,
|
|
315
|
+
violations
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private async evaluateIntentWhitelist(context: GovernanceContext): Promise<PolicyDecision> {
|
|
320
|
+
if (!this.config.allowedIntents || this.config.allowedIntents.length === 0) {
|
|
321
|
+
return { allowed: true, violations: [] };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const intent = context.metadata?.intent as string | undefined;
|
|
325
|
+
if (!intent) {
|
|
326
|
+
return { allowed: true, violations: [] };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (!this.config.allowedIntents.includes(intent)) {
|
|
330
|
+
return {
|
|
331
|
+
allowed: false,
|
|
332
|
+
violations: [{
|
|
333
|
+
policy: "intent_whitelist",
|
|
334
|
+
severity: "high",
|
|
335
|
+
message: `Intent '${intent}' is not in allowed list`
|
|
336
|
+
}]
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return { allowed: true, violations: [] };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
private async evaluateApprovalRequired(context: GovernanceContext): Promise<PolicyDecision> {
|
|
344
|
+
const approvalFor = this.config.requireApprovalFor ?? [];
|
|
345
|
+
const agent = context.agent ?? context.metadata?.agent as string | undefined;
|
|
346
|
+
|
|
347
|
+
if (!agent || !approvalFor.includes(agent)) {
|
|
348
|
+
return { allowed: true, violations: [] };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (context.metadata?.approved === true) {
|
|
352
|
+
return { allowed: true, violations: [] };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
allowed: false,
|
|
357
|
+
violations: [{
|
|
358
|
+
policy: "approval_required",
|
|
359
|
+
severity: "medium",
|
|
360
|
+
message: `Agent '${agent}' requires human approval before execution`
|
|
361
|
+
}]
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private async evaluateRateLimit(context: GovernanceContext): Promise<PolicyDecision> {
|
|
366
|
+
if (!this.config.rateLimitPerMinute) {
|
|
367
|
+
return { allowed: true, violations: [] };
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const key = context.metadata?.userId as string ?? "anonymous";
|
|
371
|
+
const now = Date.now();
|
|
372
|
+
const windowStart = now - 60000;
|
|
373
|
+
|
|
374
|
+
let requests = this.rateLimitTracker.get(key) ?? [];
|
|
375
|
+
requests = requests.filter(t => t > windowStart);
|
|
376
|
+
|
|
377
|
+
if (requests.length >= this.config.rateLimitPerMinute) {
|
|
378
|
+
return {
|
|
379
|
+
allowed: false,
|
|
380
|
+
violations: [{
|
|
381
|
+
policy: "rate_limit",
|
|
382
|
+
severity: "high",
|
|
383
|
+
message: `Rate limit exceeded: ${requests.length} requests in the last minute`
|
|
384
|
+
}]
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
requests.push(now);
|
|
389
|
+
this.rateLimitTracker.set(key, requests);
|
|
390
|
+
|
|
391
|
+
return { allowed: true, violations: [] };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private updateStats(ruleId: string, decision: PolicyDecision): void {
|
|
395
|
+
if (decision.allowed) {
|
|
396
|
+
this.stats.allowedCount++;
|
|
397
|
+
} else {
|
|
398
|
+
this.stats.blockedCount++;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
for (const violation of decision.violations) {
|
|
402
|
+
this.stats.violationsByType[ruleId] = (this.stats.violationsByType[ruleId] ?? 0) + 1;
|
|
403
|
+
this.stats.violationsBySeverity[violation.severity] = (this.stats.violationsBySeverity[violation.severity] ?? 0) + 1;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
getStats(): GovernanceStats {
|
|
408
|
+
return { ...this.stats };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
getConfig(): GovernanceConfig {
|
|
412
|
+
return { ...this.config };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
updateConfig(updates: Partial<GovernanceConfig>): void {
|
|
416
|
+
this.config = { ...this.config, ...updates };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
getRules(): GovernanceRule[] {
|
|
420
|
+
return Array.from(this.rules.values());
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
getRule(id: string): GovernanceRule | undefined {
|
|
424
|
+
return this.rules.get(id);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
resetStats(): void {
|
|
428
|
+
this.stats = {
|
|
429
|
+
totalEvaluations: 0,
|
|
430
|
+
violationsByType: {},
|
|
431
|
+
violationsBySeverity: {},
|
|
432
|
+
blockedCount: 0,
|
|
433
|
+
allowedCount: 0
|
|
434
|
+
};
|
|
435
|
+
this.rateLimitTracker.clear();
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export interface CostPolicyConfig {
|
|
440
|
+
maxPerRequest: number;
|
|
441
|
+
maxPerSession: number;
|
|
442
|
+
maxPerDay: number;
|
|
443
|
+
alertThreshold: number;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export class CostPolicy implements Policy {
|
|
447
|
+
name = "CostPolicy";
|
|
448
|
+
private sessionCosts: Map<string, number> = new Map();
|
|
449
|
+
private dailyCosts: Map<string, number> = new Map();
|
|
450
|
+
|
|
451
|
+
constructor(private config: CostPolicyConfig) {}
|
|
452
|
+
|
|
453
|
+
async validateInput(input: AgentInput): Promise<PolicyDecision> {
|
|
454
|
+
const sessionId = input.context?.sessionId as string | undefined;
|
|
455
|
+
const today = new Date().toISOString().split("T")[0];
|
|
456
|
+
|
|
457
|
+
const sessionCost = this.sessionCosts.get(sessionId ?? "default") ?? 0;
|
|
458
|
+
const dailyCost = this.dailyCosts.get(today) ?? 0;
|
|
459
|
+
|
|
460
|
+
const violations: PolicyViolation[] = [];
|
|
461
|
+
|
|
462
|
+
if (sessionCost >= this.config.maxPerSession) {
|
|
463
|
+
violations.push({
|
|
464
|
+
policy: this.name,
|
|
465
|
+
severity: "critical",
|
|
466
|
+
message: `Session cost limit reached: $${sessionCost.toFixed(4)}`
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (dailyCost >= this.config.maxPerDay) {
|
|
471
|
+
violations.push({
|
|
472
|
+
policy: this.name,
|
|
473
|
+
severity: "critical",
|
|
474
|
+
message: `Daily cost limit reached: $${dailyCost.toFixed(4)}`
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return {
|
|
479
|
+
allowed: violations.length === 0,
|
|
480
|
+
violations
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
async validateOutput(output: AgentOutput): Promise<PolicyDecision> {
|
|
485
|
+
const cost = output.costUsd ?? 0;
|
|
486
|
+
|
|
487
|
+
if (cost > this.config.maxPerRequest) {
|
|
488
|
+
return {
|
|
489
|
+
allowed: false,
|
|
490
|
+
violations: [{
|
|
491
|
+
policy: this.name,
|
|
492
|
+
severity: "high",
|
|
493
|
+
message: `Request cost $${cost.toFixed(4)} exceeds limit $${this.config.maxPerRequest}`
|
|
494
|
+
}]
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return { allowed: true, violations: [] };
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
recordCost(sessionId: string, cost: number): void {
|
|
502
|
+
const today = new Date().toISOString().split("T")[0];
|
|
503
|
+
this.sessionCosts.set(sessionId, (this.sessionCosts.get(sessionId) ?? 0) + cost);
|
|
504
|
+
this.dailyCosts.set(today, (this.dailyCosts.get(today) ?? 0) + cost);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
getSessionCost(sessionId: string): number {
|
|
508
|
+
return this.sessionCosts.get(sessionId) ?? 0;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
getDailyCost(): number {
|
|
512
|
+
const today = new Date().toISOString().split("T")[0];
|
|
513
|
+
return this.dailyCosts.get(today) ?? 0;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export interface TokenPolicyConfig {
|
|
518
|
+
maxPerRequest: number;
|
|
519
|
+
maxPerSession: number;
|
|
520
|
+
reserveForResponse: number;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export class TokenPolicy implements Policy {
|
|
524
|
+
name = "TokenPolicy";
|
|
525
|
+
private sessionTokens: Map<string, number> = new Map();
|
|
526
|
+
|
|
527
|
+
constructor(private config: TokenPolicyConfig) {}
|
|
528
|
+
|
|
529
|
+
async validateInput(input: AgentInput): Promise<PolicyDecision> {
|
|
530
|
+
const sessionId = input.context?.sessionId as string | undefined;
|
|
531
|
+
const estimatedTokens = this.estimateTokens(input.userPrompt);
|
|
532
|
+
const sessionTokens = this.sessionTokens.get(sessionId ?? "default") ?? 0;
|
|
533
|
+
|
|
534
|
+
const violations: PolicyViolation[] = [];
|
|
535
|
+
|
|
536
|
+
if (estimatedTokens > this.config.maxPerRequest) {
|
|
537
|
+
violations.push({
|
|
538
|
+
policy: this.name,
|
|
539
|
+
severity: "high",
|
|
540
|
+
message: `Request would exceed token limit: ${estimatedTokens} > ${this.config.maxPerRequest}`
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (sessionTokens + estimatedTokens > this.config.maxPerSession) {
|
|
545
|
+
violations.push({
|
|
546
|
+
policy: this.name,
|
|
547
|
+
severity: "high",
|
|
548
|
+
message: `Session token limit would be exceeded`
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return {
|
|
553
|
+
allowed: violations.length === 0,
|
|
554
|
+
violations
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
async validateOutput(output: AgentOutput): Promise<PolicyDecision> {
|
|
559
|
+
const tokens = output.usage?.totalTokens ?? 0;
|
|
560
|
+
|
|
561
|
+
if (tokens > this.config.maxPerRequest) {
|
|
562
|
+
return {
|
|
563
|
+
allowed: false,
|
|
564
|
+
violations: [{
|
|
565
|
+
policy: this.name,
|
|
566
|
+
severity: "medium",
|
|
567
|
+
message: `Token usage ${tokens} exceeds limit ${this.config.maxPerRequest}`
|
|
568
|
+
}]
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return { allowed: true, violations: [] };
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
recordTokens(sessionId: string, tokens: number): void {
|
|
576
|
+
this.sessionTokens.set(sessionId, (this.sessionTokens.get(sessionId) ?? 0) + tokens);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
private estimateTokens(text: string): number {
|
|
580
|
+
return Math.ceil(text.split(/\s+/).length * 1.3);
|
|
581
|
+
}
|
|
582
|
+
}
|