agentpay-mcp 4.1.0 → 4.1.4
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/README.md +299 -2
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -1
- package/dist/tools/otel-budget.d.ts +265 -0
- package/dist/tools/otel-budget.d.ts.map +1 -0
- package/dist/tools/otel-budget.js +399 -0
- package/dist/tools/otel-budget.js.map +1 -0
- package/dist/tools/session.d.ts.map +1 -1
- package/dist/tools/session.js +2 -0
- package/dist/tools/session.js.map +1 -1
- package/dist/tools/x402.d.ts.map +1 -1
- package/dist/tools/x402.js +58 -0
- package/dist/tools/x402.js.map +1 -1
- package/dist/utils/client.d.ts.map +1 -1
- package/dist/utils/client.js +8 -1
- package/dist/utils/client.js.map +1 -1
- package/dist/utils/x402-networks.d.ts +12 -0
- package/dist/utils/x402-networks.d.ts.map +1 -0
- package/dist/utils/x402-networks.js +30 -0
- package/dist/utils/x402-networks.js.map +1 -0
- package/docs/channel-agent-affiliate-controls.md +142 -0
- package/docs/hitl-reference-architecture.md +140 -0
- package/docs/security-posture.md +74 -0
- package/docs/trust-architecture.md +127 -0
- package/docs/vercel-deployment-hardening.md +115 -0
- package/docs/whatsapp-smb-agent-controls.md +130 -0
- package/docs/x402-batch-settlement-channels.md +199 -0
- package/docs/x402-bazaar-observability.md +209 -0
- package/docs/x402-chain-drift-compatibility.md +63 -0
- package/docs/x402-mcp-funding-ux-benchmark.md +36 -0
- package/docs/x402-multi-sdk-batch-settlement-parity.md +167 -0
- package/docs/x402-scanner-readiness.md +110 -0
- package/docs/x402-tvm-readiness.md +53 -0
- package/package.json +6 -5
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* otel-budget.ts — OpenTelemetry Budget Circuit-Breaker for AWS AgentCore
|
|
3
|
+
*
|
|
4
|
+
* Reads OTel span data from AgentCore-instrumented agents, applies per-agent
|
|
5
|
+
* and per-task budget policies against accumulated spend, emits budget
|
|
6
|
+
* enforcement decisions as OTel events, and supports circuit-breaker patterns
|
|
7
|
+
* (auto-kill agent runs exceeding budget thresholds).
|
|
8
|
+
*
|
|
9
|
+
* Why this exists: AWS AgentCore Policy Controls (GA March 2026) provide
|
|
10
|
+
* observability and guardrails but NO native per-agent/per-session spend cap
|
|
11
|
+
* APIs. This module fills that gap by sitting between the OTel telemetry
|
|
12
|
+
* pipeline and agentpay-mcp's existing budget enforcement.
|
|
13
|
+
*
|
|
14
|
+
* @module otel-budget
|
|
15
|
+
* @since 4.2.0
|
|
16
|
+
*/
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
export interface AgentBudgetPolicy {
|
|
19
|
+
/** Unique agent or session identifier */
|
|
20
|
+
agentId: string;
|
|
21
|
+
/** Optional task-level identifier for fine-grained budgets */
|
|
22
|
+
taskId?: string;
|
|
23
|
+
/** Maximum spend in USD for this agent/task */
|
|
24
|
+
maxSpendUsd: number;
|
|
25
|
+
/** Rolling window in milliseconds (0 = lifetime budget) */
|
|
26
|
+
windowMs: number;
|
|
27
|
+
/** Action when budget exceeded: 'warn' | 'block' | 'kill' */
|
|
28
|
+
breachAction: 'warn' | 'block' | 'kill';
|
|
29
|
+
/** Optional callback URL for circuit-breaker kill signal */
|
|
30
|
+
killCallbackUrl?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface SpendRecord {
|
|
33
|
+
agentId: string;
|
|
34
|
+
taskId?: string;
|
|
35
|
+
amountUsd: number;
|
|
36
|
+
timestamp: number;
|
|
37
|
+
spanId: string;
|
|
38
|
+
traceId: string;
|
|
39
|
+
}
|
|
40
|
+
export interface BudgetDecision {
|
|
41
|
+
agentId: string;
|
|
42
|
+
taskId?: string;
|
|
43
|
+
action: 'allow' | 'warn' | 'block' | 'kill';
|
|
44
|
+
accumulatedSpendUsd: number;
|
|
45
|
+
budgetLimitUsd: number;
|
|
46
|
+
remainingUsd: number;
|
|
47
|
+
utilizationPct: number;
|
|
48
|
+
reason: string;
|
|
49
|
+
timestamp: number;
|
|
50
|
+
}
|
|
51
|
+
export interface OTelSpanCostAttributes {
|
|
52
|
+
/** OTel span attribute: agentcore.agent.id */
|
|
53
|
+
'agentcore.agent.id'?: string;
|
|
54
|
+
/** OTel span attribute: agentcore.task.id */
|
|
55
|
+
'agentcore.task.id'?: string;
|
|
56
|
+
/** OTel span attribute: agentcore.cost.usd — cost incurred in this span */
|
|
57
|
+
'agentcore.cost.usd'?: number;
|
|
58
|
+
/** OTel span attribute: gen_ai.usage.input_tokens */
|
|
59
|
+
'gen_ai.usage.input_tokens'?: number;
|
|
60
|
+
/** OTel span attribute: gen_ai.usage.output_tokens */
|
|
61
|
+
'gen_ai.usage.output_tokens'?: number;
|
|
62
|
+
/** OTel span attribute: gen_ai.usage.cost */
|
|
63
|
+
'gen_ai.usage.cost'?: number;
|
|
64
|
+
/** Standard OTel trace/span IDs */
|
|
65
|
+
traceId?: string;
|
|
66
|
+
spanId?: string;
|
|
67
|
+
}
|
|
68
|
+
/** Reset all state — useful for testing */
|
|
69
|
+
export declare function _resetOTelBudgetState(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Register a budget policy for an agent or agent+task combination.
|
|
72
|
+
*/
|
|
73
|
+
export declare function registerPolicy(policy: AgentBudgetPolicy): void;
|
|
74
|
+
/**
|
|
75
|
+
* Process an OTel span and evaluate budget. Returns a BudgetDecision.
|
|
76
|
+
*
|
|
77
|
+
* This is the main entry point: feed it span attributes from an
|
|
78
|
+
* AgentCore-instrumented agent, and it returns an enforcement decision.
|
|
79
|
+
*/
|
|
80
|
+
export declare function evaluateSpan(attrs: OTelSpanCostAttributes): BudgetDecision | null;
|
|
81
|
+
export declare function invokeKillCallback(policy: AgentBudgetPolicy, decision: BudgetDecision): Promise<{
|
|
82
|
+
attempted: boolean;
|
|
83
|
+
ok: boolean;
|
|
84
|
+
status?: number;
|
|
85
|
+
error?: string;
|
|
86
|
+
} | null>;
|
|
87
|
+
/**
|
|
88
|
+
* Convert a BudgetDecision to OTel-compatible event attributes.
|
|
89
|
+
* These can be emitted as span events for AgentCore dashboard visibility.
|
|
90
|
+
*/
|
|
91
|
+
export declare function decisionToOTelEvent(decision: BudgetDecision): Record<string, string | number | boolean>;
|
|
92
|
+
/**
|
|
93
|
+
* Get recent decisions for an agent (for dashboard/audit).
|
|
94
|
+
*/
|
|
95
|
+
export declare function getDecisionHistory(agentId: string, limit?: number): BudgetDecision[];
|
|
96
|
+
/**
|
|
97
|
+
* Get all registered policies (for introspection).
|
|
98
|
+
*/
|
|
99
|
+
export declare function listPolicies(): AgentBudgetPolicy[];
|
|
100
|
+
export declare const OTelRegisterPolicySchema: z.ZodObject<{
|
|
101
|
+
agentId: z.ZodString;
|
|
102
|
+
taskId: z.ZodOptional<z.ZodString>;
|
|
103
|
+
maxSpendUsd: z.ZodNumber;
|
|
104
|
+
windowMs: z.ZodDefault<z.ZodNumber>;
|
|
105
|
+
breachAction: z.ZodDefault<z.ZodEnum<["warn", "block", "kill"]>>;
|
|
106
|
+
killCallbackUrl: z.ZodOptional<z.ZodString>;
|
|
107
|
+
}, "strip", z.ZodTypeAny, {
|
|
108
|
+
agentId: string;
|
|
109
|
+
maxSpendUsd: number;
|
|
110
|
+
windowMs: number;
|
|
111
|
+
breachAction: "block" | "warn" | "kill";
|
|
112
|
+
taskId?: string | undefined;
|
|
113
|
+
killCallbackUrl?: string | undefined;
|
|
114
|
+
}, {
|
|
115
|
+
agentId: string;
|
|
116
|
+
maxSpendUsd: number;
|
|
117
|
+
taskId?: string | undefined;
|
|
118
|
+
windowMs?: number | undefined;
|
|
119
|
+
breachAction?: "block" | "warn" | "kill" | undefined;
|
|
120
|
+
killCallbackUrl?: string | undefined;
|
|
121
|
+
}>;
|
|
122
|
+
export type OTelRegisterPolicyInput = z.infer<typeof OTelRegisterPolicySchema>;
|
|
123
|
+
export declare const otelRegisterPolicyTool: {
|
|
124
|
+
name: string;
|
|
125
|
+
description: string;
|
|
126
|
+
inputSchema: {
|
|
127
|
+
type: "object";
|
|
128
|
+
properties: {
|
|
129
|
+
agentId: {
|
|
130
|
+
type: string;
|
|
131
|
+
description: string;
|
|
132
|
+
};
|
|
133
|
+
taskId: {
|
|
134
|
+
type: string;
|
|
135
|
+
description: string;
|
|
136
|
+
};
|
|
137
|
+
maxSpendUsd: {
|
|
138
|
+
type: string;
|
|
139
|
+
description: string;
|
|
140
|
+
};
|
|
141
|
+
windowMs: {
|
|
142
|
+
type: string;
|
|
143
|
+
description: string;
|
|
144
|
+
};
|
|
145
|
+
breachAction: {
|
|
146
|
+
type: string;
|
|
147
|
+
enum: string[];
|
|
148
|
+
description: string;
|
|
149
|
+
};
|
|
150
|
+
killCallbackUrl: {
|
|
151
|
+
type: string;
|
|
152
|
+
description: string;
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
required: string[];
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
export declare function handleOTelRegisterPolicy(input: OTelRegisterPolicyInput): Promise<{
|
|
159
|
+
content: Array<{
|
|
160
|
+
type: 'text';
|
|
161
|
+
text: string;
|
|
162
|
+
}>;
|
|
163
|
+
isError?: boolean;
|
|
164
|
+
}>;
|
|
165
|
+
export declare const OTelEvaluateSpendSchema: z.ZodObject<{
|
|
166
|
+
agentId: z.ZodString;
|
|
167
|
+
taskId: z.ZodOptional<z.ZodString>;
|
|
168
|
+
costUsd: z.ZodNumber;
|
|
169
|
+
spanId: z.ZodOptional<z.ZodString>;
|
|
170
|
+
traceId: z.ZodOptional<z.ZodString>;
|
|
171
|
+
}, "strip", z.ZodTypeAny, {
|
|
172
|
+
agentId: string;
|
|
173
|
+
costUsd: number;
|
|
174
|
+
taskId?: string | undefined;
|
|
175
|
+
spanId?: string | undefined;
|
|
176
|
+
traceId?: string | undefined;
|
|
177
|
+
}, {
|
|
178
|
+
agentId: string;
|
|
179
|
+
costUsd: number;
|
|
180
|
+
taskId?: string | undefined;
|
|
181
|
+
spanId?: string | undefined;
|
|
182
|
+
traceId?: string | undefined;
|
|
183
|
+
}>;
|
|
184
|
+
export type OTelEvaluateSpendInput = z.infer<typeof OTelEvaluateSpendSchema>;
|
|
185
|
+
export declare const otelEvaluateSpendTool: {
|
|
186
|
+
name: string;
|
|
187
|
+
description: string;
|
|
188
|
+
inputSchema: {
|
|
189
|
+
type: "object";
|
|
190
|
+
properties: {
|
|
191
|
+
agentId: {
|
|
192
|
+
type: string;
|
|
193
|
+
description: string;
|
|
194
|
+
};
|
|
195
|
+
taskId: {
|
|
196
|
+
type: string;
|
|
197
|
+
description: string;
|
|
198
|
+
};
|
|
199
|
+
costUsd: {
|
|
200
|
+
type: string;
|
|
201
|
+
description: string;
|
|
202
|
+
};
|
|
203
|
+
spanId: {
|
|
204
|
+
type: string;
|
|
205
|
+
description: string;
|
|
206
|
+
};
|
|
207
|
+
traceId: {
|
|
208
|
+
type: string;
|
|
209
|
+
description: string;
|
|
210
|
+
};
|
|
211
|
+
};
|
|
212
|
+
required: string[];
|
|
213
|
+
};
|
|
214
|
+
};
|
|
215
|
+
export declare function handleOTelEvaluateSpend(input: OTelEvaluateSpendInput): Promise<{
|
|
216
|
+
content: Array<{
|
|
217
|
+
type: 'text';
|
|
218
|
+
text: string;
|
|
219
|
+
}>;
|
|
220
|
+
isError?: boolean;
|
|
221
|
+
}>;
|
|
222
|
+
export declare const OTelBudgetStatusSchema: z.ZodObject<{
|
|
223
|
+
agentId: z.ZodString;
|
|
224
|
+
includeHistory: z.ZodDefault<z.ZodBoolean>;
|
|
225
|
+
historyLimit: z.ZodDefault<z.ZodNumber>;
|
|
226
|
+
}, "strip", z.ZodTypeAny, {
|
|
227
|
+
agentId: string;
|
|
228
|
+
includeHistory: boolean;
|
|
229
|
+
historyLimit: number;
|
|
230
|
+
}, {
|
|
231
|
+
agentId: string;
|
|
232
|
+
includeHistory?: boolean | undefined;
|
|
233
|
+
historyLimit?: number | undefined;
|
|
234
|
+
}>;
|
|
235
|
+
export type OTelBudgetStatusInput = z.infer<typeof OTelBudgetStatusSchema>;
|
|
236
|
+
export declare const otelBudgetStatusTool: {
|
|
237
|
+
name: string;
|
|
238
|
+
description: string;
|
|
239
|
+
inputSchema: {
|
|
240
|
+
type: "object";
|
|
241
|
+
properties: {
|
|
242
|
+
agentId: {
|
|
243
|
+
type: string;
|
|
244
|
+
description: string;
|
|
245
|
+
};
|
|
246
|
+
includeHistory: {
|
|
247
|
+
type: string;
|
|
248
|
+
description: string;
|
|
249
|
+
};
|
|
250
|
+
historyLimit: {
|
|
251
|
+
type: string;
|
|
252
|
+
description: string;
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
required: string[];
|
|
256
|
+
};
|
|
257
|
+
};
|
|
258
|
+
export declare function handleOTelBudgetStatus(input: OTelBudgetStatusInput): Promise<{
|
|
259
|
+
content: Array<{
|
|
260
|
+
type: 'text';
|
|
261
|
+
text: string;
|
|
262
|
+
}>;
|
|
263
|
+
isError?: boolean;
|
|
264
|
+
}>;
|
|
265
|
+
//# sourceMappingURL=otel-budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otel-budget.d.ts","sourceRoot":"","sources":["../../src/tools/otel-budget.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* otel-budget.ts — OpenTelemetry Budget Circuit-Breaker for AWS AgentCore
|
|
4
|
+
*
|
|
5
|
+
* Reads OTel span data from AgentCore-instrumented agents, applies per-agent
|
|
6
|
+
* and per-task budget policies against accumulated spend, emits budget
|
|
7
|
+
* enforcement decisions as OTel events, and supports circuit-breaker patterns
|
|
8
|
+
* (auto-kill agent runs exceeding budget thresholds).
|
|
9
|
+
*
|
|
10
|
+
* Why this exists: AWS AgentCore Policy Controls (GA March 2026) provide
|
|
11
|
+
* observability and guardrails but NO native per-agent/per-session spend cap
|
|
12
|
+
* APIs. This module fills that gap by sitting between the OTel telemetry
|
|
13
|
+
* pipeline and agentpay-mcp's existing budget enforcement.
|
|
14
|
+
*
|
|
15
|
+
* @module otel-budget
|
|
16
|
+
* @since 4.2.0
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.otelBudgetStatusTool = exports.OTelBudgetStatusSchema = exports.otelEvaluateSpendTool = exports.OTelEvaluateSpendSchema = exports.otelRegisterPolicyTool = exports.OTelRegisterPolicySchema = void 0;
|
|
20
|
+
exports._resetOTelBudgetState = _resetOTelBudgetState;
|
|
21
|
+
exports.registerPolicy = registerPolicy;
|
|
22
|
+
exports.evaluateSpan = evaluateSpan;
|
|
23
|
+
exports.invokeKillCallback = invokeKillCallback;
|
|
24
|
+
exports.decisionToOTelEvent = decisionToOTelEvent;
|
|
25
|
+
exports.getDecisionHistory = getDecisionHistory;
|
|
26
|
+
exports.listPolicies = listPolicies;
|
|
27
|
+
exports.handleOTelRegisterPolicy = handleOTelRegisterPolicy;
|
|
28
|
+
exports.handleOTelEvaluateSpend = handleOTelEvaluateSpend;
|
|
29
|
+
exports.handleOTelBudgetStatus = handleOTelBudgetStatus;
|
|
30
|
+
const zod_1 = require("zod");
|
|
31
|
+
const format_js_1 = require("../utils/format.js");
|
|
32
|
+
// ─── In-memory stores ──────────────────────────────────────────────────────
|
|
33
|
+
const _policies = new Map();
|
|
34
|
+
const _spendLedger = [];
|
|
35
|
+
const _decisions = [];
|
|
36
|
+
/** Build a policy key from agentId + optional taskId */
|
|
37
|
+
function policyKey(agentId, taskId) {
|
|
38
|
+
return taskId ? `${agentId}::${taskId}` : agentId;
|
|
39
|
+
}
|
|
40
|
+
/** Reset all state — useful for testing */
|
|
41
|
+
function _resetOTelBudgetState() {
|
|
42
|
+
_policies.clear();
|
|
43
|
+
_spendLedger.length = 0;
|
|
44
|
+
_decisions.length = 0;
|
|
45
|
+
}
|
|
46
|
+
// ─── Core Logic ────────────────────────────────────────────────────────────
|
|
47
|
+
/**
|
|
48
|
+
* Register a budget policy for an agent or agent+task combination.
|
|
49
|
+
*/
|
|
50
|
+
function registerPolicy(policy) {
|
|
51
|
+
const key = policyKey(policy.agentId, policy.taskId);
|
|
52
|
+
_policies.set(key, policy);
|
|
53
|
+
}
|
|
54
|
+
function getApplicablePolicy(agentId, taskId) {
|
|
55
|
+
const taskPolicy = taskId ? _policies.get(policyKey(agentId, taskId)) : undefined;
|
|
56
|
+
const agentPolicy = _policies.get(policyKey(agentId));
|
|
57
|
+
return taskPolicy ?? agentPolicy;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get accumulated spend for an agent within the policy's rolling window.
|
|
61
|
+
*/
|
|
62
|
+
function getAccumulatedSpend(agentId, taskId, windowMs) {
|
|
63
|
+
const now = Date.now();
|
|
64
|
+
const cutoff = windowMs && windowMs > 0 ? now - windowMs : 0;
|
|
65
|
+
return _spendLedger
|
|
66
|
+
.filter((r) => r.agentId === agentId &&
|
|
67
|
+
(taskId === undefined || r.taskId === taskId) &&
|
|
68
|
+
r.timestamp >= cutoff)
|
|
69
|
+
.reduce((sum, r) => sum + r.amountUsd, 0);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Process an OTel span and evaluate budget. Returns a BudgetDecision.
|
|
73
|
+
*
|
|
74
|
+
* This is the main entry point: feed it span attributes from an
|
|
75
|
+
* AgentCore-instrumented agent, and it returns an enforcement decision.
|
|
76
|
+
*/
|
|
77
|
+
function evaluateSpan(attrs) {
|
|
78
|
+
const agentId = attrs['agentcore.agent.id'];
|
|
79
|
+
if (!agentId)
|
|
80
|
+
return null;
|
|
81
|
+
const taskId = attrs['agentcore.task.id'];
|
|
82
|
+
const costUsd = attrs['agentcore.cost.usd'] ?? attrs['gen_ai.usage.cost'] ?? 0;
|
|
83
|
+
if (costUsd <= 0)
|
|
84
|
+
return null;
|
|
85
|
+
// Record spend
|
|
86
|
+
const record = {
|
|
87
|
+
agentId,
|
|
88
|
+
taskId,
|
|
89
|
+
amountUsd: costUsd,
|
|
90
|
+
timestamp: Date.now(),
|
|
91
|
+
spanId: attrs.spanId ?? 'unknown',
|
|
92
|
+
traceId: attrs.traceId ?? 'unknown',
|
|
93
|
+
};
|
|
94
|
+
_spendLedger.push(record);
|
|
95
|
+
// Find applicable policy (task-specific first, then agent-level)
|
|
96
|
+
const policy = getApplicablePolicy(agentId, taskId);
|
|
97
|
+
if (!policy) {
|
|
98
|
+
// No policy = allow (but we still recorded the spend)
|
|
99
|
+
return {
|
|
100
|
+
agentId,
|
|
101
|
+
taskId,
|
|
102
|
+
action: 'allow',
|
|
103
|
+
accumulatedSpendUsd: getAccumulatedSpend(agentId, taskId),
|
|
104
|
+
budgetLimitUsd: Infinity,
|
|
105
|
+
remainingUsd: Infinity,
|
|
106
|
+
utilizationPct: 0,
|
|
107
|
+
reason: 'No budget policy registered for this agent',
|
|
108
|
+
timestamp: Date.now(),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const spendScopeTaskId = policy.taskId === undefined ? undefined : taskId;
|
|
112
|
+
const accumulated = getAccumulatedSpend(agentId, spendScopeTaskId, policy.windowMs);
|
|
113
|
+
const remaining = Math.max(0, policy.maxSpendUsd - accumulated);
|
|
114
|
+
const utilization = (accumulated / policy.maxSpendUsd) * 100;
|
|
115
|
+
let action = 'allow';
|
|
116
|
+
let reason = 'Within budget';
|
|
117
|
+
if (accumulated > policy.maxSpendUsd) {
|
|
118
|
+
action = policy.breachAction;
|
|
119
|
+
reason = `Budget exceeded: $${accumulated.toFixed(4)} / $${policy.maxSpendUsd.toFixed(4)} (${utilization.toFixed(1)}%)`;
|
|
120
|
+
}
|
|
121
|
+
else if (utilization >= 90) {
|
|
122
|
+
action = 'warn';
|
|
123
|
+
reason = `Approaching budget limit: $${accumulated.toFixed(4)} / $${policy.maxSpendUsd.toFixed(4)} (${utilization.toFixed(1)}%)`;
|
|
124
|
+
}
|
|
125
|
+
const decision = {
|
|
126
|
+
agentId,
|
|
127
|
+
taskId,
|
|
128
|
+
action,
|
|
129
|
+
accumulatedSpendUsd: accumulated,
|
|
130
|
+
budgetLimitUsd: policy.maxSpendUsd,
|
|
131
|
+
remainingUsd: remaining,
|
|
132
|
+
utilizationPct: utilization,
|
|
133
|
+
reason,
|
|
134
|
+
timestamp: Date.now(),
|
|
135
|
+
};
|
|
136
|
+
_decisions.push(decision);
|
|
137
|
+
return decision;
|
|
138
|
+
}
|
|
139
|
+
async function invokeKillCallback(policy, decision) {
|
|
140
|
+
if (decision.action !== 'kill' || !policy.killCallbackUrl) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
const response = await fetch(policy.killCallbackUrl, {
|
|
145
|
+
method: 'POST',
|
|
146
|
+
headers: {
|
|
147
|
+
'content-type': 'application/json',
|
|
148
|
+
},
|
|
149
|
+
body: JSON.stringify({
|
|
150
|
+
event: 'agentpay.budget.kill',
|
|
151
|
+
decision,
|
|
152
|
+
policy: {
|
|
153
|
+
agentId: policy.agentId,
|
|
154
|
+
taskId: policy.taskId,
|
|
155
|
+
maxSpendUsd: policy.maxSpendUsd,
|
|
156
|
+
windowMs: policy.windowMs,
|
|
157
|
+
breachAction: policy.breachAction,
|
|
158
|
+
},
|
|
159
|
+
}),
|
|
160
|
+
});
|
|
161
|
+
return {
|
|
162
|
+
attempted: true,
|
|
163
|
+
ok: response.ok,
|
|
164
|
+
status: response.status,
|
|
165
|
+
...(response.ok ? {} : { error: `Kill callback returned HTTP ${response.status}` }),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
return {
|
|
170
|
+
attempted: true,
|
|
171
|
+
ok: false,
|
|
172
|
+
error: error instanceof Error ? error.message : String(error),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Convert a BudgetDecision to OTel-compatible event attributes.
|
|
178
|
+
* These can be emitted as span events for AgentCore dashboard visibility.
|
|
179
|
+
*/
|
|
180
|
+
function decisionToOTelEvent(decision) {
|
|
181
|
+
return {
|
|
182
|
+
'event.name': 'agentpay.budget.decision',
|
|
183
|
+
'agentpay.agent_id': decision.agentId,
|
|
184
|
+
'agentpay.task_id': decision.taskId ?? '',
|
|
185
|
+
'agentpay.action': decision.action,
|
|
186
|
+
'agentpay.accumulated_spend_usd': decision.accumulatedSpendUsd,
|
|
187
|
+
'agentpay.budget_limit_usd': decision.budgetLimitUsd,
|
|
188
|
+
'agentpay.remaining_usd': decision.remainingUsd,
|
|
189
|
+
'agentpay.utilization_pct': decision.utilizationPct,
|
|
190
|
+
'agentpay.reason': decision.reason,
|
|
191
|
+
'agentpay.circuit_breaker_tripped': decision.action === 'kill',
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Get recent decisions for an agent (for dashboard/audit).
|
|
196
|
+
*/
|
|
197
|
+
function getDecisionHistory(agentId, limit = 50) {
|
|
198
|
+
return _decisions
|
|
199
|
+
.filter((d) => d.agentId === agentId)
|
|
200
|
+
.slice(-limit);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Get all registered policies (for introspection).
|
|
204
|
+
*/
|
|
205
|
+
function listPolicies() {
|
|
206
|
+
return Array.from(_policies.values());
|
|
207
|
+
}
|
|
208
|
+
// ─── MCP Tool Definitions ──────────────────────────────────────────────────
|
|
209
|
+
exports.OTelRegisterPolicySchema = zod_1.z.object({
|
|
210
|
+
agentId: zod_1.z.string().describe('Agent or session identifier from AgentCore'),
|
|
211
|
+
taskId: zod_1.z.string().optional().describe('Optional task-level identifier'),
|
|
212
|
+
maxSpendUsd: zod_1.z.number().positive().describe('Maximum spend in USD'),
|
|
213
|
+
windowMs: zod_1.z
|
|
214
|
+
.number()
|
|
215
|
+
.min(0)
|
|
216
|
+
.default(0)
|
|
217
|
+
.describe('Rolling window in ms (0 = lifetime budget)'),
|
|
218
|
+
breachAction: zod_1.z
|
|
219
|
+
.enum(['warn', 'block', 'kill'])
|
|
220
|
+
.default('block')
|
|
221
|
+
.describe('Action on budget breach: warn, block, or kill (circuit-breaker)'),
|
|
222
|
+
killCallbackUrl: zod_1.z
|
|
223
|
+
.string()
|
|
224
|
+
.optional()
|
|
225
|
+
.describe('Webhook URL to invoke when circuit-breaker trips (kill action)'),
|
|
226
|
+
});
|
|
227
|
+
exports.otelRegisterPolicyTool = {
|
|
228
|
+
name: 'otel_register_budget_policy',
|
|
229
|
+
description: 'Register a budget policy for an AWS AgentCore agent or task. ' +
|
|
230
|
+
'When OTel spans report costs exceeding this budget, agentpay-mcp will ' +
|
|
231
|
+
'enforce the configured action (warn/block/kill circuit-breaker). ' +
|
|
232
|
+
'This fills the gap left by AgentCore Policy Controls which provide ' +
|
|
233
|
+
'observability but no native per-agent spend caps.',
|
|
234
|
+
inputSchema: {
|
|
235
|
+
type: 'object',
|
|
236
|
+
properties: {
|
|
237
|
+
agentId: { type: 'string', description: 'Agent/session ID from AgentCore' },
|
|
238
|
+
taskId: { type: 'string', description: 'Optional task-level ID' },
|
|
239
|
+
maxSpendUsd: { type: 'number', description: 'Max spend in USD' },
|
|
240
|
+
windowMs: { type: 'number', description: 'Rolling window in ms (0 = lifetime)' },
|
|
241
|
+
breachAction: {
|
|
242
|
+
type: 'string',
|
|
243
|
+
enum: ['warn', 'block', 'kill'],
|
|
244
|
+
description: 'Action on breach',
|
|
245
|
+
},
|
|
246
|
+
killCallbackUrl: { type: 'string', description: 'Circuit-breaker webhook URL' },
|
|
247
|
+
},
|
|
248
|
+
required: ['agentId', 'maxSpendUsd'],
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
async function handleOTelRegisterPolicy(input) {
|
|
252
|
+
try {
|
|
253
|
+
const policy = {
|
|
254
|
+
agentId: input.agentId,
|
|
255
|
+
taskId: input.taskId,
|
|
256
|
+
maxSpendUsd: input.maxSpendUsd,
|
|
257
|
+
windowMs: input.windowMs ?? 0,
|
|
258
|
+
breachAction: input.breachAction ?? 'block',
|
|
259
|
+
killCallbackUrl: input.killCallbackUrl,
|
|
260
|
+
};
|
|
261
|
+
registerPolicy(policy);
|
|
262
|
+
return {
|
|
263
|
+
content: [
|
|
264
|
+
(0, format_js_1.textContent)(JSON.stringify({
|
|
265
|
+
success: true,
|
|
266
|
+
policy: {
|
|
267
|
+
key: policyKey(policy.agentId, policy.taskId),
|
|
268
|
+
...policy,
|
|
269
|
+
},
|
|
270
|
+
})),
|
|
271
|
+
],
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
return {
|
|
276
|
+
content: [(0, format_js_1.textContent)((0, format_js_1.formatError)(error, 'otel_register_budget_policy'))],
|
|
277
|
+
isError: true,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// ─── otel_evaluate_spend ───────────────────────────────────────────────────
|
|
282
|
+
exports.OTelEvaluateSpendSchema = zod_1.z.object({
|
|
283
|
+
agentId: zod_1.z.string().describe('Agent ID from OTel span attribute agentcore.agent.id'),
|
|
284
|
+
taskId: zod_1.z.string().optional().describe('Task ID from OTel span attribute agentcore.task.id'),
|
|
285
|
+
costUsd: zod_1.z.number().describe('Cost in USD from this span'),
|
|
286
|
+
spanId: zod_1.z.string().optional().describe('OTel span ID'),
|
|
287
|
+
traceId: zod_1.z.string().optional().describe('OTel trace ID'),
|
|
288
|
+
});
|
|
289
|
+
exports.otelEvaluateSpendTool = {
|
|
290
|
+
name: 'otel_evaluate_spend',
|
|
291
|
+
description: 'Evaluate a spend event from an OTel span against registered budget policies. ' +
|
|
292
|
+
'Returns a budget decision (allow/warn/block/kill) with utilization details. ' +
|
|
293
|
+
'The decision is also formatted as OTel event attributes for re-emission ' +
|
|
294
|
+
'into the AgentCore telemetry pipeline.',
|
|
295
|
+
inputSchema: {
|
|
296
|
+
type: 'object',
|
|
297
|
+
properties: {
|
|
298
|
+
agentId: { type: 'string', description: 'Agent ID from OTel span' },
|
|
299
|
+
taskId: { type: 'string', description: 'Task ID from OTel span' },
|
|
300
|
+
costUsd: { type: 'number', description: 'Span cost in USD' },
|
|
301
|
+
spanId: { type: 'string', description: 'OTel span ID' },
|
|
302
|
+
traceId: { type: 'string', description: 'OTel trace ID' },
|
|
303
|
+
},
|
|
304
|
+
required: ['agentId', 'costUsd'],
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
async function handleOTelEvaluateSpend(input) {
|
|
308
|
+
try {
|
|
309
|
+
const policy = getApplicablePolicy(input.agentId, input.taskId);
|
|
310
|
+
const decision = evaluateSpan({
|
|
311
|
+
'agentcore.agent.id': input.agentId,
|
|
312
|
+
'agentcore.task.id': input.taskId,
|
|
313
|
+
'agentcore.cost.usd': input.costUsd,
|
|
314
|
+
spanId: input.spanId,
|
|
315
|
+
traceId: input.traceId,
|
|
316
|
+
});
|
|
317
|
+
if (!decision) {
|
|
318
|
+
return {
|
|
319
|
+
content: [
|
|
320
|
+
(0, format_js_1.textContent)(JSON.stringify({ action: 'skip', reason: 'No cost or agent ID in span' })),
|
|
321
|
+
],
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
const otelEvent = decisionToOTelEvent(decision);
|
|
325
|
+
const killCallback = policy
|
|
326
|
+
? await invokeKillCallback(policy, decision)
|
|
327
|
+
: null;
|
|
328
|
+
return {
|
|
329
|
+
content: [
|
|
330
|
+
(0, format_js_1.textContent)(JSON.stringify({
|
|
331
|
+
decision,
|
|
332
|
+
otelEvent,
|
|
333
|
+
killCallback,
|
|
334
|
+
})),
|
|
335
|
+
],
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
return {
|
|
340
|
+
content: [(0, format_js_1.textContent)((0, format_js_1.formatError)(error, 'otel_evaluate_spend'))],
|
|
341
|
+
isError: true,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// ─── otel_budget_status ────────────────────────────────────────────────────
|
|
346
|
+
exports.OTelBudgetStatusSchema = zod_1.z.object({
|
|
347
|
+
agentId: zod_1.z.string().describe('Agent ID to check budget status for'),
|
|
348
|
+
includeHistory: zod_1.z
|
|
349
|
+
.boolean()
|
|
350
|
+
.default(false)
|
|
351
|
+
.describe('Include recent decision history'),
|
|
352
|
+
historyLimit: zod_1.z.number().default(20).describe('Max history entries to return'),
|
|
353
|
+
});
|
|
354
|
+
exports.otelBudgetStatusTool = {
|
|
355
|
+
name: 'otel_budget_status',
|
|
356
|
+
description: 'Get the current budget status for an AgentCore agent, including ' +
|
|
357
|
+
'accumulated spend, remaining budget, utilization percentage, and ' +
|
|
358
|
+
'optional decision history. Useful for dashboards and audit trails.',
|
|
359
|
+
inputSchema: {
|
|
360
|
+
type: 'object',
|
|
361
|
+
properties: {
|
|
362
|
+
agentId: { type: 'string', description: 'Agent ID' },
|
|
363
|
+
includeHistory: { type: 'boolean', description: 'Include decision history' },
|
|
364
|
+
historyLimit: { type: 'number', description: 'Max history entries' },
|
|
365
|
+
},
|
|
366
|
+
required: ['agentId'],
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
async function handleOTelBudgetStatus(input) {
|
|
370
|
+
try {
|
|
371
|
+
const agentPolicies = Array.from(_policies.values()).filter((p) => p.agentId === input.agentId);
|
|
372
|
+
const statuses = agentPolicies.map((policy) => {
|
|
373
|
+
const accumulated = getAccumulatedSpend(policy.agentId, policy.taskId, policy.windowMs);
|
|
374
|
+
return {
|
|
375
|
+
policyKey: policyKey(policy.agentId, policy.taskId),
|
|
376
|
+
policy,
|
|
377
|
+
accumulatedSpendUsd: accumulated,
|
|
378
|
+
remainingUsd: Math.max(0, policy.maxSpendUsd - accumulated),
|
|
379
|
+
utilizationPct: (accumulated / policy.maxSpendUsd) * 100,
|
|
380
|
+
};
|
|
381
|
+
});
|
|
382
|
+
const result = {
|
|
383
|
+
agentId: input.agentId,
|
|
384
|
+
policies: statuses,
|
|
385
|
+
totalAccumulatedUsd: getAccumulatedSpend(input.agentId),
|
|
386
|
+
};
|
|
387
|
+
if (input.includeHistory) {
|
|
388
|
+
result.recentDecisions = getDecisionHistory(input.agentId, input.historyLimit ?? 20);
|
|
389
|
+
}
|
|
390
|
+
return { content: [(0, format_js_1.textContent)(JSON.stringify(result))] };
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
return {
|
|
394
|
+
content: [(0, format_js_1.textContent)((0, format_js_1.formatError)(error, 'otel_budget_status'))],
|
|
395
|
+
isError: true,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
//# sourceMappingURL=otel-budget.js.map
|