forked from kindrat86/agentshield
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathagentshield-plugin.ts
More file actions
118 lines (105 loc) · 3 KB
/
Copy pathagentshield-plugin.ts
File metadata and controls
118 lines (105 loc) · 3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// AgentShield Plugin for OpenClaw
// Per-transaction spend rules enforced before model dispatch
//
// Architecture:
// OpenClaw gateway → AgentShield plugin → model dispatch
// Each call evaluated against 7 composable rules in <1ms
//
// Config in openclaw.json:
// "plugins": {
// "agentshield": {
// "endpoint": "https://agentshield.fly.dev",
// "apiKey": "${AGENTSHIELD_API_KEY}",
// "rules": {
// "transactionLimit": 100,
// "dailyCap": 2000,
// "velocityThreshold": 10,
// "merchantAllowlist": ["openai-api", "anthropic-api"],
// "categoryBlocks": []
// }
// }
// }
export interface AgentShieldConfig {
endpoint: string;
apiKey: string;
rules: {
transactionLimit?: number;
dailyCap?: number;
velocityThreshold?: number;
merchantAllowlist?: string[];
categoryBlocks?: string[];
};
}
interface AgentShieldDecision {
decision: 'ALLOW' | 'BLOCK' | 'FLAGGED';
rule: string;
evaluation_ms: number;
}
export class AgentShieldPlugin {
private config: AgentShieldConfig;
constructor(config: AgentShieldConfig) {
this.config = config;
}
async evaluate(transaction: {
amount: number;
merchant: string;
agent_id: string;
category?: string;
}): Promise<AgentShieldDecision> {
const response = await fetch(
`${this.config.endpoint}/v1/transactions/evaluate`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.config.apiKey}`,
},
body: JSON.stringify({
amount: transaction.amount,
merchant: transaction.merchant,
agent_id: transaction.agent_id,
rules: this.config.rules,
}),
}
);
if (!response.ok) {
throw new Error(`AgentShield evaluation failed: ${response.status}`);
}
return response.json();
}
// Hook into OpenClaw's model dispatch pipeline
// Intercepts before each LLM call, evaluates rules, returns allow/block
async beforeModelDispatch(params: {
model: string;
estimatedTokens: number;
provider: string;
sessionCost: number;
}): Promise<{ allowed: boolean; reason?: string }> {
const estimatedCost = this.estimateCost(params.model, params.estimatedTokens);
const decision = await this.evaluate({
amount: estimatedCost,
merchant: params.provider,
agent_id: 'openclaw-session',
category: 'llm-api',
});
if (decision.decision === 'BLOCK') {
return {
allowed: false,
reason: `Blocked by ${decision.rule} (${decision.evaluation_ms}ms)`,
};
}
return { allowed: true };
}
private estimateCost(model: string, tokens: number): number {
const rates: Record<string, number> = {
'claude-opus': 15.0,
'claude-sonnet': 3.0,
'claude-haiku': 0.25,
'gpt-4o': 2.5,
'gpt-4': 30.0,
'gpt-3.5-turbo': 0.5,
};
const rate = rates[model] || 5.0;
return (tokens / 1_000_000) * rate;
}
}