@adhd/agent-plugin-budget 0.2.1 → 0.2.2

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.
Files changed (4) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/index.cjs +7 -9
  3. package/index.js +529 -703
  4. package/package.json +2 -2
package/index.js CHANGED
@@ -1,716 +1,542 @@
1
- import { z as u } from "zod";
2
- import { contextWindowFor as S } from "@adhd/agent-base-types";
3
- function E(c) {
4
- const t = /^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/, e = c.match(t);
5
- if (!e)
6
- throw new Error(`invalid ISO 8601 duration: ${c}`);
7
- const [, o, s, n, i, r, a] = e;
8
- let l = 0;
9
- return o && (l += parseInt(o) * 365.25 * 864e5), s && (l += parseInt(s) * 30.44 * 864e5), n && (l += parseInt(n) * 864e5), i && (l += parseInt(i) * 36e5), r && (l += parseInt(r) * 6e4), a && (l += parseFloat(a) * 1e3), Math.round(l);
1
+ import { z as e } from "zod";
2
+ import { contextWindowFor as t } from "@adhd/agent-base-types";
3
+ //#region src/index.ts
4
+ function n(e) {
5
+ let t = e.match(/^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/);
6
+ if (!t) throw Error(`invalid ISO 8601 duration: ${e}`);
7
+ let [, n, r, i, a, o, s] = t, c = 0;
8
+ return n && (c += parseInt(n) * 365.25 * 864e5), r && (c += parseInt(r) * 30.44 * 864e5), i && (c += parseInt(i) * 864e5), a && (c += parseInt(a) * 36e5), o && (c += parseInt(o) * 6e4), s && (c += parseFloat(s) * 1e3), Math.round(c);
10
9
  }
11
- const M = [
12
- "context",
13
- "inputTokens",
14
- "outputTokens",
15
- "calls",
16
- "wallClock",
17
- "modelMs",
18
- "cost",
19
- "toolCalls",
20
- "errors",
21
- "consecutiveErrors",
22
- "responseSize"
23
- ], b = /* @__PURE__ */ new Set(["toolCalls", "errors", "consecutiveErrors"]), y = u.object({
24
- field: u.enum(M),
25
- maximum: u.number().min(0).optional(),
26
- contextWindowFraction: u.number().min(0).max(1).optional(),
27
- window: u.string().optional(),
28
- scope: u.enum(["task", "session", "agent", "global"]).optional(),
29
- mode: u.enum(["warning", "block"]).optional(),
30
- message: u.string().optional()
31
- }).superRefine((c, t) => {
32
- if (c.field === "context") {
33
- const e = c.maximum !== void 0, o = c.contextWindowFraction !== void 0;
34
- e === o && t.addIssue({
35
- code: u.ZodIssueCode.custom,
36
- path: ["contextWindowFraction"],
37
- message: "cap field 'context' requires exactly one of 'maximum' or 'contextWindowFraction'"
38
- }), c.window !== void 0 && t.addIssue({
39
- code: u.ZodIssueCode.custom,
40
- path: ["window"],
41
- message: "cap field 'context' rejects 'window' — a windowed peak is meaningless; windowed cumulative volume is expressible via 'inputTokens'/'outputTokens' + 'window'"
42
- });
43
- } else
44
- (c.field === "errors" || c.field === "consecutiveErrors") && (c.window !== void 0 && t.addIssue({
45
- code: u.ZodIssueCode.custom,
46
- path: ["window"],
47
- message: `cap field '${c.field}' rejects 'window' — windowed error budgets are not expressible this wave`
48
- }), c.scope !== void 0 && c.scope !== "task" && t.addIssue({
49
- code: u.ZodIssueCode.custom,
50
- path: ["scope"],
51
- message: `cap field '${c.field}' is counted per-task in memory (no task_usage column) — only 'task' scope is expressible this wave`
52
- })), c.window !== void 0 && (c.scope === void 0 || c.scope === "task") && t.addIssue({
53
- code: u.ZodIssueCode.custom,
54
- path: ["scope"],
55
- message: `cap field '${c.field}' carries 'window' but resolves to 'task' scope, where windowed caps are a silent no-op (no task-level window query); set an explicit 'scope': 'session' | 'agent' | 'global'`
56
- }), c.contextWindowFraction !== void 0 && t.addIssue({
57
- code: u.ZodIssueCode.custom,
58
- path: ["contextWindowFraction"],
59
- message: `'contextWindowFraction' is only valid on cap field 'context' (got '${c.field}')`
60
- }), c.maximum === void 0 && t.addIssue({
61
- code: u.ZodIssueCode.custom,
62
- path: ["maximum"],
63
- message: `cap field '${c.field}' requires a 'maximum'`
64
- });
65
- }), g = u.object({
66
- caps: u.array(y).optional(),
67
- mode: u.enum(["warning", "block"]).optional(),
68
- costPerInputToken: u.number().min(0).optional(),
69
- costPerOutputToken: u.number().min(0).optional(),
70
- // Cache-weighted cost rates (BUG-AGENTMCP-008). Default = costPerInputToken when
71
- // unset, so a config without them bills every input token at the flat input rate —
72
- // byte-for-byte identical to the pre-fix behavior.
73
- costPerCacheReadToken: u.number().min(0).optional(),
74
- costPerCacheWriteToken: u.number().min(0).optional(),
75
- scope: u.enum(["task", "session", "agent", "global"]).optional()
76
- }), x = g.partial().superRefine((c, t) => {
77
- const e = c.caps ?? [];
78
- for (const [o, s] of e.entries())
79
- s.field === "context" && t.addIssue({
80
- code: u.ZodIssueCode.custom,
81
- path: ["caps", o, "field"],
82
- message: "cap field 'context' is only valid at model scope — a tool-scoped 'context' cap is a silent no-op (enforced on the model path, invisible to tool overrides); place it in 'defaults'/'agent'/'provider' instead"
83
- });
84
- }), _ = u.object({
85
- defaults: g.optional(),
86
- agent: u.object({
87
- default: g.optional(),
88
- overrides: u.record(u.string(), g.partial()).optional().default({})
89
- }).optional(),
90
- provider: u.object({
91
- default: g.optional(),
92
- overrides: u.record(u.string(), g.partial()).optional().default({})
93
- }).optional(),
94
- tool: u.object({
95
- default: x.optional(),
96
- overrides: u.record(u.string(), x).optional().default({})
97
- }).optional()
98
- }), N = u.object({}).passthrough(), A = {
99
- maxInputTokens: { field: "inputTokens" },
100
- maxOutputTokens: { field: "outputTokens" },
101
- maxModelCalls: { field: "calls" },
102
- maxWallClockMs: { field: "wallClock" },
103
- maxModelMs: { field: "modelMs" },
104
- maxCostUSD: { field: "cost" },
105
- maxTokensPer24h: { field: "inputTokens", window: "PT24H" },
106
- maxCalls: { field: "toolCalls" }
10
+ var r = [
11
+ "context",
12
+ "inputTokens",
13
+ "outputTokens",
14
+ "calls",
15
+ "wallClock",
16
+ "modelMs",
17
+ "cost",
18
+ "toolCalls",
19
+ "errors",
20
+ "consecutiveErrors",
21
+ "responseSize"
22
+ ], i = /* @__PURE__ */ new Set([
23
+ "toolCalls",
24
+ "errors",
25
+ "consecutiveErrors"
26
+ ]), a = e.object({
27
+ field: e.enum(r),
28
+ maximum: e.number().min(0).optional(),
29
+ contextWindowFraction: e.number().min(0).max(1).optional(),
30
+ window: e.string().optional(),
31
+ scope: e.enum([
32
+ "task",
33
+ "session",
34
+ "agent",
35
+ "global"
36
+ ]).optional(),
37
+ mode: e.enum(["warning", "block"]).optional(),
38
+ message: e.string().optional()
39
+ }).superRefine((t, n) => {
40
+ t.field === "context" ? (t.maximum !== void 0 == (t.contextWindowFraction !== void 0) && n.addIssue({
41
+ code: e.ZodIssueCode.custom,
42
+ path: ["contextWindowFraction"],
43
+ message: "cap field 'context' requires exactly one of 'maximum' or 'contextWindowFraction'"
44
+ }), t.window !== void 0 && n.addIssue({
45
+ code: e.ZodIssueCode.custom,
46
+ path: ["window"],
47
+ message: "cap field 'context' rejects 'window' — a windowed peak is meaningless; windowed cumulative volume is expressible via 'inputTokens'/'outputTokens' + 'window'"
48
+ })) : ((t.field === "errors" || t.field === "consecutiveErrors") && (t.window !== void 0 && n.addIssue({
49
+ code: e.ZodIssueCode.custom,
50
+ path: ["window"],
51
+ message: `cap field '${t.field}' rejects 'window' — windowed error budgets are not expressible this wave`
52
+ }), t.scope !== void 0 && t.scope !== "task" && n.addIssue({
53
+ code: e.ZodIssueCode.custom,
54
+ path: ["scope"],
55
+ message: `cap field '${t.field}' is counted per-task in memory (no task_usage column) — only 'task' scope is expressible this wave`
56
+ })), t.window !== void 0 && (t.scope === void 0 || t.scope === "task") && n.addIssue({
57
+ code: e.ZodIssueCode.custom,
58
+ path: ["scope"],
59
+ message: `cap field '${t.field}' carries 'window' but resolves to 'task' scope, where windowed caps are a silent no-op (no task-level window query); set an explicit 'scope': 'session' | 'agent' | 'global'`
60
+ }), t.contextWindowFraction !== void 0 && n.addIssue({
61
+ code: e.ZodIssueCode.custom,
62
+ path: ["contextWindowFraction"],
63
+ message: `'contextWindowFraction' is only valid on cap field 'context' (got '${t.field}')`
64
+ }), t.maximum === void 0 && n.addIssue({
65
+ code: e.ZodIssueCode.custom,
66
+ path: ["maximum"],
67
+ message: `cap field '${t.field}' requires a 'maximum'`
68
+ }));
69
+ }), o = e.object({
70
+ caps: e.array(a).optional(),
71
+ mode: e.enum(["warning", "block"]).optional(),
72
+ costPerInputToken: e.number().min(0).optional(),
73
+ costPerOutputToken: e.number().min(0).optional(),
74
+ costPerCacheReadToken: e.number().min(0).optional(),
75
+ costPerCacheWriteToken: e.number().min(0).optional(),
76
+ scope: e.enum([
77
+ "task",
78
+ "session",
79
+ "agent",
80
+ "global"
81
+ ]).optional()
82
+ }), s = o.partial().superRefine((t, n) => {
83
+ let r = t.caps ?? [];
84
+ for (let [t, i] of r.entries()) i.field === "context" && n.addIssue({
85
+ code: e.ZodIssueCode.custom,
86
+ path: [
87
+ "caps",
88
+ t,
89
+ "field"
90
+ ],
91
+ message: "cap field 'context' is only valid at model scope — a tool-scoped 'context' cap is a silent no-op (enforced on the model path, invisible to tool overrides); place it in 'defaults'/'agent'/'provider' instead"
92
+ });
93
+ }), c = e.object({
94
+ defaults: o.optional(),
95
+ agent: e.object({
96
+ default: o.optional(),
97
+ overrides: e.record(e.string(), o.partial()).optional().default({})
98
+ }).optional(),
99
+ provider: e.object({
100
+ default: o.optional(),
101
+ overrides: e.record(e.string(), o.partial()).optional().default({})
102
+ }).optional(),
103
+ tool: e.object({
104
+ default: s.optional(),
105
+ overrides: e.record(e.string(), s).optional().default({})
106
+ }).optional()
107
+ }), l = e.object({}).passthrough(), u = {
108
+ maxInputTokens: { field: "inputTokens" },
109
+ maxOutputTokens: { field: "outputTokens" },
110
+ maxModelCalls: { field: "calls" },
111
+ maxWallClockMs: { field: "wallClock" },
112
+ maxModelMs: { field: "modelMs" },
113
+ maxCostUSD: { field: "cost" },
114
+ maxTokensPer24h: {
115
+ field: "inputTokens",
116
+ window: "PT24H"
117
+ },
118
+ maxCalls: { field: "toolCalls" }
107
119
  };
108
- function O(c) {
109
- const t = [], e = {};
110
- for (const [n, i] of Object.entries(c)) {
111
- const r = A[n];
112
- if (r && typeof i == "number") {
113
- const a = { field: r.field, maximum: i };
114
- r.window && (a.window = r.window), t.push(a);
115
- } else
116
- (n === "scope" || n === "mode" || n === "costPerInputToken" || n === "costPerOutputToken" || n === "costPerCacheReadToken" || n === "costPerCacheWriteToken" || n === "message") && (e[n] = i);
117
- }
118
- t.length > 0 && (e.caps = t);
119
- const o = e.scope, s = o === "task" || o === "session" || o === "agent" || o === "global" ? o : void 0;
120
- for (const n of t)
121
- n.window !== void 0 && n.scope === void 0 && (n.scope = s ?? "global");
122
- return g.parse(e);
120
+ function d(e) {
121
+ let t = [], n = {};
122
+ for (let [r, i] of Object.entries(e)) {
123
+ let e = u[r];
124
+ if (e && typeof i == "number") {
125
+ let n = {
126
+ field: e.field,
127
+ maximum: i
128
+ };
129
+ e.window && (n.window = e.window), t.push(n);
130
+ } else (r === "scope" || r === "mode" || r === "costPerInputToken" || r === "costPerOutputToken" || r === "costPerCacheReadToken" || r === "costPerCacheWriteToken" || r === "message") && (n[r] = i);
131
+ }
132
+ t.length > 0 && (n.caps = t);
133
+ let r = n.scope, i = r === "task" || r === "session" || r === "agent" || r === "global" ? r : void 0;
134
+ for (let e of t) e.window !== void 0 && e.scope === void 0 && (e.scope = i ?? "global");
135
+ return o.parse(n);
123
136
  }
124
- const w = (c) => `legacy 'tokens' budget cap detected (${c}): cap field 'tokens' is removed; use 'context' (peak request input) or 'inputTokens'/'outputTokens' (windowed volume)`;
125
- function P(c) {
126
- const t = c ?? {};
127
- if (typeof t.maxTotalTokens == "number")
128
- throw new Error(w("flat 'maxTotalTokens'"));
129
- const e = (s) => {
130
- if (typeof s != "object" || s === null)
131
- return;
132
- const n = s;
133
- if (typeof n.maxTotalTokens == "number")
134
- throw new Error(w("structured 'maxTotalTokens'"));
135
- const i = n.caps;
136
- if (Array.isArray(i)) {
137
- for (const r of i)
138
- if (typeof r == "object" && r !== null && r.field === "tokens")
139
- throw new Error(w("caps[].field === 'tokens'"));
140
- }
141
- }, o = (s) => {
142
- if (typeof s != "object" || s === null)
143
- return;
144
- const n = s;
145
- e(n), "default" in n && e(n.default);
146
- const i = n.overrides;
147
- if (typeof i == "object" && i !== null)
148
- for (const r of Object.values(i))
149
- e(r);
150
- };
151
- e(t.defaults), o(t.agent), o(t.provider), o(t.tool);
137
+ var f = (e) => `legacy 'tokens' budget cap detected (${e}): cap field 'tokens' is removed; use 'context' (peak request input) or 'inputTokens'/'outputTokens' (windowed volume)`;
138
+ function p(e) {
139
+ let t = e ?? {};
140
+ if (typeof t.maxTotalTokens == "number") throw Error(f("flat 'maxTotalTokens'"));
141
+ let n = (e) => {
142
+ if (typeof e != "object" || !e) return;
143
+ let t = e;
144
+ if (typeof t.maxTotalTokens == "number") throw Error(f("structured 'maxTotalTokens'"));
145
+ let n = t.caps;
146
+ if (Array.isArray(n)) {
147
+ for (let e of n) if (typeof e == "object" && e && e.field === "tokens") throw Error(f("caps[].field === 'tokens'"));
148
+ }
149
+ }, r = (e) => {
150
+ if (typeof e != "object" || !e) return;
151
+ let t = e;
152
+ n(t), "default" in t && n(t.default);
153
+ let r = t.overrides;
154
+ if (typeof r == "object" && r) for (let e of Object.values(r)) n(e);
155
+ };
156
+ n(t.defaults), r(t.agent), r(t.provider), r(t.tool);
152
157
  }
153
- function I(c) {
154
- P(c);
155
- const t = c;
156
- if (t.defaults !== void 0 || t.agent !== void 0 || t.provider !== void 0 || t.tool !== void 0) {
157
- const e = _.parse(c);
158
- return {
159
- defaults: e.defaults ?? g.parse({}),
160
- agent: e.agent ?? { overrides: {} },
161
- provider: e.provider ?? { overrides: {} },
162
- tool: e.tool ?? { overrides: {} }
163
- };
164
- }
165
- return {
166
- defaults: O(t),
167
- agent: { overrides: {} },
168
- provider: { overrides: {} },
169
- tool: { overrides: {} }
170
- };
158
+ function m(e) {
159
+ p(e);
160
+ let t = e;
161
+ if (t.defaults !== void 0 || t.agent !== void 0 || t.provider !== void 0 || t.tool !== void 0) {
162
+ let t = c.parse(e);
163
+ return {
164
+ defaults: t.defaults ?? o.parse({}),
165
+ agent: t.agent ?? { overrides: {} },
166
+ provider: t.provider ?? { overrides: {} },
167
+ tool: t.tool ?? { overrides: {} }
168
+ };
169
+ }
170
+ return {
171
+ defaults: d(t),
172
+ agent: { overrides: {} },
173
+ provider: { overrides: {} },
174
+ tool: { overrides: {} }
175
+ };
171
176
  }
172
- function v(c, t, e, o) {
173
- return {
174
- isEnforcementError: !0,
175
- code: "BUDGET_EXCEEDED",
176
- message: o ?? `${c} limit is ${t}, current value is ${Math.round(e)}`
177
- };
177
+ function h(e, t, n, r) {
178
+ return {
179
+ isEnforcementError: !0,
180
+ code: "BUDGET_EXCEEDED",
181
+ message: r ?? `${e} limit is ${t}, current value is ${Math.round(n)}`
182
+ };
178
183
  }
179
- function $(c, t, e) {
180
- return { isToolWarning: !0, toolName: c, callId: t, message: e };
184
+ function g(e, t, n) {
185
+ return {
186
+ isToolWarning: !0,
187
+ toolName: e,
188
+ callId: t,
189
+ message: n
190
+ };
181
191
  }
182
- function R(c, t) {
183
- var o;
184
- let e = 0;
185
- for (const s of c) {
186
- e += ((o = s.content) == null ? void 0 : o.length) ?? 0;
187
- for (const n of s.toolCalls ?? [])
188
- e += JSON.stringify(n.arguments ?? {}).length;
189
- for (const n of s.toolResults ?? [])
190
- e += JSON.stringify(n.result ?? null).length;
191
- }
192
- for (const s of t)
193
- e += JSON.stringify({
194
- name: s.name,
195
- description: s.description,
196
- inputSchema: s.inputSchema
197
- }).length;
198
- return Math.ceil(e / 4);
192
+ function _(e, t) {
193
+ let n = 0;
194
+ for (let t of e) {
195
+ n += t.content?.length ?? 0;
196
+ for (let e of t.toolCalls ?? []) n += JSON.stringify(e.arguments ?? {}).length;
197
+ for (let e of t.toolResults ?? []) n += JSON.stringify(e.result ?? null).length;
198
+ }
199
+ for (let e of t) n += JSON.stringify({
200
+ name: e.name,
201
+ description: e.description,
202
+ inputSchema: e.inputSchema
203
+ }).length;
204
+ return Math.ceil(n / 4);
199
205
  }
200
- class L {
201
- constructor(t, e, o = 0, s = 0, n = 0, i = 0) {
202
- this.db = t, this.cfg = e, this.costPerInput = o, this.costPerOutput = s, this.costPerCacheRead = n, this.costPerCacheWrite = i, this.name = "agent-mcp-budget", this.accumulators = /* @__PURE__ */ new Map();
203
- }
204
- install(t) {
205
- this.hooks = t, t.register("task:start", (e) => {
206
- try {
207
- this.onTaskStart(e);
208
- } catch {
209
- }
210
- }), t.register("pre:model_request", (e) => {
211
- try {
212
- this.onPreModelRequest(e);
213
- } catch {
214
- }
215
- }), t.register("post:model_response", (e) => {
216
- try {
217
- this.onPostModelResponse(e);
218
- } catch {
219
- }
220
- }), t.register("post:tool_call", (e) => {
221
- try {
222
- this.onPostToolCall(e);
223
- } catch {
224
- }
225
- }), t.register("task:completed", (e) => {
226
- try {
227
- this.onTerminal(e.executionContext.taskId);
228
- } catch {
229
- }
230
- }), t.register("task:failed", (e) => {
231
- try {
232
- this.onTerminal(e.executionContext.taskId);
233
- } catch {
234
- }
235
- }), t.register("task:cancelled", (e) => {
236
- try {
237
- this.onTerminal(e.executionContext.taskId);
238
- } catch {
239
- }
240
- }), t.registerEnforcement(
241
- "pre:model_request",
242
- (e) => this.enforcePreModel(e)
243
- ), t.registerEnforcement("pre:tool_call", (e) => this.enforcePreTool(e)), t.register("transform:tool_result", (e) => {
244
- try {
245
- this.enforceResponseSize(e);
246
- } catch {
247
- }
248
- });
249
- }
250
- // ── Observational handlers ────────────────────────────────────────────────
251
- onTaskStart(t) {
252
- var i, r;
253
- const { taskId: e, sessionId: o, agentName: s } = t.executionContext, n = ((r = (i = t.executionContext.agentDefinition) == null ? void 0 : i.provider) == null ? void 0 : r.type) ?? "unknown";
254
- this.accumulators.set(e, {
255
- taskId: e,
256
- sessionId: o ?? void 0,
257
- agentName: s,
258
- providerType: n,
259
- startedAtMs: Date.now(),
260
- uncachedInputTokens: 0,
261
- cacheReadTokens: 0,
262
- cacheCreationTokens: 0,
263
- inputTokens: 0,
264
- outputTokens: 0,
265
- peakContextTokens: 0,
266
- modelCalls: 0,
267
- totalModelMs: 0,
268
- toolCalls: /* @__PURE__ */ new Map(),
269
- errors: 0,
270
- consecutiveErrors: 0
271
- });
272
- }
273
- onPreModelRequest(t) {
274
- const e = this.accumulators.get(t.executionContext.taskId);
275
- e && (e.modelCallStartMs = Date.now());
276
- }
277
- onPostModelResponse(t) {
278
- const e = this.accumulators.get(t.executionContext.taskId);
279
- if (!e)
280
- return;
281
- const o = t.tokenUsage;
282
- if (o) {
283
- const s = o.inputTokens ?? 0, n = o.cacheReadTokens ?? 0, i = o.cacheCreationTokens ?? 0, a = o.uncachedInputTokens !== void 0 || o.cacheReadTokens !== void 0 || o.cacheCreationTokens !== void 0 ? o.uncachedInputTokens ?? Math.max(0, s - n - i) : s;
284
- e.uncachedInputTokens += a, e.cacheReadTokens += n, e.cacheCreationTokens += i, e.inputTokens += a + n + i, e.outputTokens += o.outputTokens ?? 0, e.peakContextTokens = Math.max(e.peakContextTokens, s);
285
- }
286
- e.modelCalls += 1, e.modelCallStartMs !== void 0 && (e.totalModelMs += Date.now() - e.modelCallStartMs, e.modelCallStartMs = void 0);
287
- }
288
- onTerminal(t) {
289
- this.accumulators.delete(t);
290
- }
291
- /**
292
- * Packet C — error counters (plan §3): the ONLY place the error budget
293
- * changes. Fires at post:tool_call, which the orchestrator emits exclusively
294
- * for EXECUTED tools (orchestrator.ts Phase 2 map, post:tool_call emit). A
295
- * call soft-blocked by an IToolWarning at pre:tool_call is injected as a
296
- * warningResult and SKIPPED (orchestrator.ts:619-626, filter at 645-647) —
297
- * it never reaches Phase 2 and never emits post:tool_call. That is the
298
- * non-self-amplifying guarantee: a warning-mode errors cap's own warnings are
299
- * never counted, so the counter cannot feed itself.
300
- *
301
- * Thrown-errors-only (owner lean, plan §3): `isError` is true at this
302
- * boundary only when the tool's callTool THREW (orchestrator.ts:718);
303
- * error-shaped non-throwing results are indistinguishable from success here.
304
- */
305
- onPostToolCall(t) {
306
- const e = this.accumulators.get(t.executionContext.taskId);
307
- e && (t.isError ? (e.errors += 1, e.consecutiveErrors += 1) : e.consecutiveErrors = 0);
308
- }
309
- // ── Config resolution ─────────────────────────────────────────────────────
310
- mergeDim(t) {
311
- let e = { caps: [] };
312
- for (const o of t)
313
- o && (e = {
314
- caps: [...e.caps ?? [], ...o.caps ?? []],
315
- mode: o.mode ?? e.mode,
316
- costPerInputToken: o.costPerInputToken ?? e.costPerInputToken,
317
- costPerOutputToken: o.costPerOutputToken ?? e.costPerOutputToken,
318
- costPerCacheReadToken: o.costPerCacheReadToken ?? e.costPerCacheReadToken,
319
- costPerCacheWriteToken: o.costPerCacheWriteToken ?? e.costPerCacheWriteToken,
320
- scope: o.scope ?? e.scope
321
- });
322
- return e;
323
- }
324
- resolveCaps(t, e, o) {
325
- var f, d, k;
326
- const s = this.cfg.defaults, n = this.cfg.agent, i = this.cfg.provider, r = this.cfg.tool;
327
- if (o) {
328
- const m = (f = r == null ? void 0 : r.overrides) == null ? void 0 : f[o], h = this.mergeDim([s, r == null ? void 0 : r.default, m]);
329
- return {
330
- caps: h.caps ?? [],
331
- mode: h.mode,
332
- scope: h.scope
333
- };
334
- }
335
- const a = (d = n == null ? void 0 : n.overrides) == null ? void 0 : d[t], l = (k = i == null ? void 0 : i.overrides) == null ? void 0 : k[e], p = this.mergeDim([
336
- s,
337
- n == null ? void 0 : n.default,
338
- a,
339
- i == null ? void 0 : i.default,
340
- l
341
- ]);
342
- return { caps: p.caps ?? [], mode: p.mode, scope: p.scope };
343
- }
344
- // ── Scope-aware DB queries ────────────────────────────────────────────────
345
- queryScopeTotals(t, e, o, s) {
346
- const n = this.accumulators.get(t), i = n ? {
347
- inputTokens: n.inputTokens,
348
- outputTokens: n.outputTokens,
349
- modelCalls: n.modelCalls,
350
- peakContextTokens: n.peakContextTokens
351
- } : {
352
- inputTokens: 0,
353
- outputTokens: 0,
354
- modelCalls: 0,
355
- peakContextTokens: 0
356
- };
357
- if (s === "task" || !this.db)
358
- return i;
359
- try {
360
- const r = this.db;
361
- let a;
362
- if (s === "session" && e ? a = r.prepare(
363
- `SELECT
364
- COALESCE(SUM(tu.input_tokens), 0) AS input,
365
- COALESCE(SUM(tu.output_tokens), 0) AS output,
366
- COALESCE(SUM(tu.model_calls), 0) AS calls,
367
- COALESCE(MAX(tu.peak_context_tokens), 0) AS peak
206
+ var v = class {
207
+ constructor(e, t, n = 0, r = 0, i = 0, a = 0) {
208
+ this.db = e, this.cfg = t, this.costPerInput = n, this.costPerOutput = r, this.costPerCacheRead = i, this.costPerCacheWrite = a, this.name = "agent-mcp-budget", this.accumulators = /* @__PURE__ */ new Map();
209
+ }
210
+ install(e) {
211
+ this.hooks = e, e.register("task:start", (e) => {
212
+ try {
213
+ this.onTaskStart(e);
214
+ } catch {}
215
+ }), e.register("pre:model_request", (e) => {
216
+ try {
217
+ this.onPreModelRequest(e);
218
+ } catch {}
219
+ }), e.register("post:model_response", (e) => {
220
+ try {
221
+ this.onPostModelResponse(e);
222
+ } catch {}
223
+ }), e.register("post:tool_call", (e) => {
224
+ try {
225
+ this.onPostToolCall(e);
226
+ } catch {}
227
+ }), e.register("task:completed", (e) => {
228
+ try {
229
+ this.onTerminal(e.executionContext.taskId);
230
+ } catch {}
231
+ }), e.register("task:failed", (e) => {
232
+ try {
233
+ this.onTerminal(e.executionContext.taskId);
234
+ } catch {}
235
+ }), e.register("task:cancelled", (e) => {
236
+ try {
237
+ this.onTerminal(e.executionContext.taskId);
238
+ } catch {}
239
+ }), e.registerEnforcement("pre:model_request", (e) => this.enforcePreModel(e)), e.registerEnforcement("pre:tool_call", (e) => this.enforcePreTool(e)), e.register("transform:tool_result", (e) => {
240
+ try {
241
+ this.enforceResponseSize(e);
242
+ } catch {}
243
+ });
244
+ }
245
+ onTaskStart(e) {
246
+ let { taskId: t, sessionId: n, agentName: r } = e.executionContext, i = e.executionContext.agentDefinition?.provider?.type ?? "unknown";
247
+ this.accumulators.set(t, {
248
+ taskId: t,
249
+ sessionId: n ?? void 0,
250
+ agentName: r,
251
+ providerType: i,
252
+ startedAtMs: Date.now(),
253
+ uncachedInputTokens: 0,
254
+ cacheReadTokens: 0,
255
+ cacheCreationTokens: 0,
256
+ inputTokens: 0,
257
+ outputTokens: 0,
258
+ peakContextTokens: 0,
259
+ modelCalls: 0,
260
+ totalModelMs: 0,
261
+ toolCalls: /* @__PURE__ */ new Map(),
262
+ errors: 0,
263
+ consecutiveErrors: 0
264
+ });
265
+ }
266
+ onPreModelRequest(e) {
267
+ let t = this.accumulators.get(e.executionContext.taskId);
268
+ t && (t.modelCallStartMs = Date.now());
269
+ }
270
+ onPostModelResponse(e) {
271
+ let t = this.accumulators.get(e.executionContext.taskId);
272
+ if (!t) return;
273
+ let n = e.tokenUsage;
274
+ if (n) {
275
+ let e = n.inputTokens ?? 0, r = n.cacheReadTokens ?? 0, i = n.cacheCreationTokens ?? 0, a = n.uncachedInputTokens !== void 0 || n.cacheReadTokens !== void 0 || n.cacheCreationTokens !== void 0 ? n.uncachedInputTokens ?? Math.max(0, e - r - i) : e;
276
+ t.uncachedInputTokens += a, t.cacheReadTokens += r, t.cacheCreationTokens += i, t.inputTokens += a + r + i, t.outputTokens += n.outputTokens ?? 0, t.peakContextTokens = Math.max(t.peakContextTokens, e);
277
+ }
278
+ t.modelCalls += 1, t.modelCallStartMs !== void 0 && (t.totalModelMs += Date.now() - t.modelCallStartMs, t.modelCallStartMs = void 0);
279
+ }
280
+ onTerminal(e) {
281
+ this.accumulators.delete(e);
282
+ }
283
+ onPostToolCall(e) {
284
+ let t = this.accumulators.get(e.executionContext.taskId);
285
+ t && (e.isError ? (t.errors += 1, t.consecutiveErrors += 1) : t.consecutiveErrors = 0);
286
+ }
287
+ mergeDim(e) {
288
+ let t = { caps: [] };
289
+ for (let n of e) n && (t = {
290
+ caps: [...t.caps ?? [], ...n.caps ?? []],
291
+ mode: n.mode ?? t.mode,
292
+ costPerInputToken: n.costPerInputToken ?? t.costPerInputToken,
293
+ costPerOutputToken: n.costPerOutputToken ?? t.costPerOutputToken,
294
+ costPerCacheReadToken: n.costPerCacheReadToken ?? t.costPerCacheReadToken,
295
+ costPerCacheWriteToken: n.costPerCacheWriteToken ?? t.costPerCacheWriteToken,
296
+ scope: n.scope ?? t.scope
297
+ });
298
+ return t;
299
+ }
300
+ resolveCaps(e, t, n) {
301
+ let r = this.cfg.defaults, i = this.cfg.agent, a = this.cfg.provider, o = this.cfg.tool;
302
+ if (n) {
303
+ let e = o?.overrides?.[n], t = this.mergeDim([
304
+ r,
305
+ o?.default,
306
+ e
307
+ ]);
308
+ return {
309
+ caps: t.caps ?? [],
310
+ mode: t.mode,
311
+ scope: t.scope
312
+ };
313
+ }
314
+ let s = i?.overrides?.[e], c = a?.overrides?.[t], l = this.mergeDim([
315
+ r,
316
+ i?.default,
317
+ s,
318
+ a?.default,
319
+ c
320
+ ]);
321
+ return {
322
+ caps: l.caps ?? [],
323
+ mode: l.mode,
324
+ scope: l.scope
325
+ };
326
+ }
327
+ queryScopeTotals(e, t, n, r) {
328
+ let i = this.accumulators.get(e), a = i ? {
329
+ inputTokens: i.inputTokens,
330
+ outputTokens: i.outputTokens,
331
+ modelCalls: i.modelCalls,
332
+ peakContextTokens: i.peakContextTokens
333
+ } : {
334
+ inputTokens: 0,
335
+ outputTokens: 0,
336
+ modelCalls: 0,
337
+ peakContextTokens: 0
338
+ };
339
+ if (r === "task" || !this.db) return a;
340
+ try {
341
+ let i = this.db, o;
342
+ if (r === "session" && t ? o = i.prepare("SELECT\n COALESCE(SUM(tu.input_tokens), 0) AS input,\n COALESCE(SUM(tu.output_tokens), 0) AS output,\n COALESCE(SUM(tu.model_calls), 0) AS calls,\n COALESCE(MAX(tu.peak_context_tokens), 0) AS peak\n FROM task_usage tu\n JOIN tasks t ON tu.task_id = t.id\n WHERE t.session_id = ? AND tu.task_id != ?").get(t, e) : r === "agent" ? o = i.prepare("SELECT\n COALESCE(SUM(input_tokens), 0) AS input,\n COALESCE(SUM(output_tokens), 0) AS output,\n COALESCE(SUM(model_calls), 0) AS calls,\n COALESCE(MAX(peak_context_tokens), 0) AS peak\n FROM task_usage\n WHERE agent_name = ? AND task_id != ?").get(n, e) : r === "global" && (o = i.prepare("SELECT\n COALESCE(SUM(input_tokens), 0) AS input,\n COALESCE(SUM(output_tokens), 0) AS output,\n COALESCE(SUM(model_calls), 0) AS calls,\n COALESCE(MAX(peak_context_tokens), 0) AS peak\n FROM task_usage\n WHERE task_id != ?").get(e)), o) return {
343
+ inputTokens: (o.input ?? 0) + a.inputTokens,
344
+ outputTokens: (o.output ?? 0) + a.outputTokens,
345
+ modelCalls: (o.calls ?? 0) + a.modelCalls,
346
+ peakContextTokens: Math.max(o.peak ?? 0, a.peakContextTokens)
347
+ };
348
+ } catch {}
349
+ return a;
350
+ }
351
+ queryWindowTokens(e, t, n, r) {
352
+ if (!this.db) return 0;
353
+ try {
354
+ let i = this.db, a = new Date(Date.now() - n).toISOString(), o;
355
+ if (e === "session") {
356
+ let e = r ? " AND tu.task_id != ?" : "", n = [t, a];
357
+ r && n.push(r), o = i.prepare(`SELECT COALESCE(SUM(tu.input_tokens + tu.output_tokens), 0) AS total
368
358
  FROM task_usage tu
369
359
  JOIN tasks t ON tu.task_id = t.id
370
- WHERE t.session_id = ? AND tu.task_id != ?`
371
- ).get(e, t) : s === "agent" ? a = r.prepare(
372
- `SELECT
373
- COALESCE(SUM(input_tokens), 0) AS input,
374
- COALESCE(SUM(output_tokens), 0) AS output,
375
- COALESCE(SUM(model_calls), 0) AS calls,
376
- COALESCE(MAX(peak_context_tokens), 0) AS peak
360
+ WHERE t.session_id = ? AND tu.created_at >= ?${e}`).get(...n);
361
+ } else if (e === "agent") {
362
+ let e = r ? " AND task_id != ?" : "", n = [t, a];
363
+ r && n.push(r), o = i.prepare(`SELECT COALESCE(SUM(input_tokens + output_tokens), 0) AS total
377
364
  FROM task_usage
378
- WHERE agent_name = ? AND task_id != ?`
379
- ).get(o, t) : s === "global" && (a = r.prepare(
380
- `SELECT
381
- COALESCE(SUM(input_tokens), 0) AS input,
382
- COALESCE(SUM(output_tokens), 0) AS output,
383
- COALESCE(SUM(model_calls), 0) AS calls,
384
- COALESCE(MAX(peak_context_tokens), 0) AS peak
365
+ WHERE agent_name = ? AND created_at >= ?${e}`).get(...n);
366
+ } else if (e === "global") {
367
+ let e = r ? " AND task_id != ?" : "", t = [a];
368
+ r && t.push(r), o = i.prepare(`SELECT COALESCE(SUM(input_tokens + output_tokens), 0) AS total
385
369
  FROM task_usage
386
- WHERE task_id != ?`
387
- ).get(t)), a)
388
- return {
389
- inputTokens: (a.input ?? 0) + i.inputTokens,
390
- outputTokens: (a.output ?? 0) + i.outputTokens,
391
- modelCalls: (a.calls ?? 0) + i.modelCalls,
392
- // Scoped context = MAX over the scope's task set of peak_context_tokens,
393
- // with the current task's in-memory peak folded in (it is a member of the
394
- // set; the queries above exclude it by `task_id != ?`).
395
- peakContextTokens: Math.max(a.peak ?? 0, i.peakContextTokens)
396
- };
397
- } catch {
398
- }
399
- return i;
400
- }
401
- queryWindowTokens(t, e, o, s) {
402
- if (!this.db)
403
- return 0;
404
- try {
405
- const n = this.db, i = new Date(Date.now() - o).toISOString();
406
- let r;
407
- if (t === "session") {
408
- const a = s ? " AND tu.task_id != ?" : "", l = [e, i];
409
- s && l.push(s), r = n.prepare(
410
- `SELECT COALESCE(SUM(tu.input_tokens + tu.output_tokens), 0) AS total
411
- FROM task_usage tu
412
- JOIN tasks t ON tu.task_id = t.id
413
- WHERE t.session_id = ? AND tu.created_at >= ?${a}`
414
- ).get(...l);
415
- } else if (t === "agent") {
416
- const a = s ? " AND task_id != ?" : "", l = [e, i];
417
- s && l.push(s), r = n.prepare(
418
- `SELECT COALESCE(SUM(input_tokens + output_tokens), 0) AS total
419
- FROM task_usage
420
- WHERE agent_name = ? AND created_at >= ?${a}`
421
- ).get(...l);
422
- } else if (t === "global") {
423
- const a = s ? " AND task_id != ?" : "", l = [i];
424
- s && l.push(s), r = n.prepare(
425
- `SELECT COALESCE(SUM(input_tokens + output_tokens), 0) AS total
426
- FROM task_usage
427
- WHERE created_at >= ?${a}`
428
- ).get(...l);
429
- }
430
- return (r == null ? void 0 : r.total) ?? 0;
431
- } catch {
432
- return 0;
433
- }
434
- }
435
- // ── Generic cap evaluation ────────────────────────────────────────────────
436
- // ── Single-shot usage snapshot ──────────────────────────────────────────
437
- /**
438
- * Build a complete usage snapshot for the current enforcement event.
439
- *
440
- * Makes exactly `U + W` DB queries where:
441
- * U = number of unique non-task scopes across all caps (0..3)
442
- * W = number of unique (scope, window) pairs across all caps
443
- *
444
- * Independent of cap count, agent count, session count, or history depth.
445
- *
446
- * `requestEstimate` (tokens) is the tools-aware estimate of the PENDING
447
- * request, computed on the model path; it feeds the 'context' enforcement
448
- * value = max(provider-reported peak, estimate) per owner ruling 4.
449
- */
450
- buildSnapshot(t, e, o, s, n, i, r = 0) {
451
- const a = {};
452
- a.inputTokens = e.inputTokens, a.outputTokens = e.outputTokens, a.calls = e.modelCalls, a.wallClock = Date.now() - e.startedAtMs, a.modelMs = e.totalModelMs, a.context = Math.max(e.peakContextTokens, r), a.errors = e.errors, a.consecutiveErrors = e.consecutiveErrors, a.cost = e.uncachedInputTokens * this.costPerInput + e.cacheReadTokens * this.costPerCacheRead + e.cacheCreationTokens * this.costPerCacheWrite + e.outputTokens * this.costPerOutput;
453
- const l = /* @__PURE__ */ new Set(), p = /* @__PURE__ */ new Map();
454
- for (const f of t) {
455
- const d = f.scope ?? i ?? "task";
456
- if (d !== "task" && l.add(d), f.window) {
457
- const k = `${d}:${f.window}`;
458
- p.has(k) || p.set(k, {
459
- scope: d,
460
- windowMs: E(f.window)
461
- });
462
- }
463
- }
464
- for (const f of l) {
465
- const d = this.queryScopeTotals(o, s, n, f);
466
- a[`${f}:inputTokens`] = d.inputTokens, a[`${f}:outputTokens`] = d.outputTokens, a[`${f}:calls`] = d.modelCalls, a[`${f}:context`] = Math.max(
467
- d.peakContextTokens,
468
- r
469
- );
470
- }
471
- for (const [f, { scope: d, windowMs: k }] of p) {
472
- let m = "";
473
- d === "session" ? m = s ?? "" : d === "agent" && (m = n ?? ""), a[f] = this.queryWindowTokens(d, m, k, o);
474
- }
475
- return a;
476
- }
477
- getSnapshotValue(t, e, o) {
478
- const s = e.scope ?? o ?? "task";
479
- let n;
480
- e.window ? n = "" : n = s !== "task" ? `${s}:` : "";
481
- let i;
482
- switch (e.field) {
483
- case "inputTokens":
484
- i = t[`${n}inputTokens`] ?? t.inputTokens;
485
- break;
486
- case "outputTokens":
487
- i = t[`${n}outputTokens`] ?? t.outputTokens;
488
- break;
489
- case "context":
490
- i = t[`${n}context`] ?? t.context;
491
- break;
492
- case "calls":
493
- i = t[`${n}calls`] ?? t.calls;
494
- break;
495
- case "wallClock":
496
- i = t.wallClock;
497
- break;
498
- case "modelMs":
499
- i = t.modelMs;
500
- break;
501
- case "cost":
502
- i = t.cost;
503
- break;
504
- case "toolCalls":
505
- i = 0;
506
- break;
507
- case "errors":
508
- i = t.errors;
509
- break;
510
- case "consecutiveErrors":
511
- i = t.consecutiveErrors;
512
- break;
513
- default:
514
- i = 0;
515
- }
516
- return e.window && (i += t[`${s}:${e.window}`] ?? 0), i;
517
- }
518
- /**
519
- * Emit a `budget:*` notification event for an exceeded cap. Pure notification
520
- * layer (owner ruling 7): HookRegistry.emit swallows handler errors and is a
521
- * no-op when no handler is registered, so emission can never affect
522
- * enforcement. `message` defaults to cap.message, then the standard
523
- * `"<field> limit is <limit>, current value is <current>"` string — the
524
- * same resolution `makeEnforcementError` uses, so the block event's message
525
- * always matches the error the orchestrator sees.
526
- *
527
- * `limit` is the RESOLVED cap limit (`maximum`, or the context-window-derived
528
- * limit for `contextWindowFraction` caps) — the payload must report the real
529
- * number the cap trips at, not an undefined `maximum`.
530
- */
531
- async emitBudgetEvent(t, e, o, s, n, i) {
532
- this.hooks && await this.hooks.emit(t, {
533
- executionContext: e,
534
- field: o.field,
535
- maximum: n,
536
- current: s,
537
- message: i ?? o.message ?? `${o.field} limit is ${n}, current value is ${Math.round(
538
- s
539
- )}`
540
- });
541
- }
542
- /**
543
- * Resolve a cap's numeric limit. A `context` cap configured via
544
- * `contextWindowFraction` has no `maximum`: the limit is
545
- * `contextWindowFor(modelId) * fraction` (128K fallback for unknown models).
546
- * Every other cap carries an explicit `maximum` (schema-enforced).
547
- */
548
- resolveCapLimit(t, e) {
549
- if (t.maximum !== void 0)
550
- return t.maximum;
551
- const o = e.agentDefinition.provider;
552
- return Math.floor(
553
- S(o.model) * (t.contextWindowFraction ?? 0)
554
- );
555
- }
556
- /**
557
- * Evaluate a single cap against the snapshot. Mode resolution matches the
558
- * tool path (`cap.mode ?? dimMode ?? 'warning'`, owner ruling 5): warning
559
- * (the default) emits `budget:warning` and resolves — the run continues;
560
- * block emits `budget:block` BEFORE throwing the enforcement error.
561
- */
562
- async evaluateCap(t, e, o, s, n) {
563
- const i = this.resolveCapLimit(t, o), r = this.getSnapshotValue(e, t, n);
564
- if (r < i)
565
- return;
566
- if ((t.mode ?? s ?? "warning") === "warning") {
567
- await this.emitBudgetEvent("budget:warning", o, t, r, i);
568
- return;
569
- }
570
- throw await this.emitBudgetEvent("budget:block", o, t, r, i), v(t.field, i, r, t.message);
571
- }
572
- // ── Enforcement: pre:model_request ────────────────────────────────────────
573
- async enforcePreModel(t) {
574
- var d, k;
575
- const { taskId: e, sessionId: o, agentName: s } = t.executionContext, n = ((k = (d = t.executionContext.agentDefinition) == null ? void 0 : d.provider) == null ? void 0 : k.type) ?? "unknown", i = this.accumulators.get(e);
576
- if (!i)
577
- return;
578
- const { caps: r, mode: a, scope: l } = this.resolveCaps(
579
- s,
580
- n
581
- ), p = r.filter(
582
- (m) => !b.has(m.field)
583
- );
584
- if (p.length === 0)
585
- return;
586
- const f = this.buildSnapshot(
587
- p,
588
- i,
589
- e,
590
- o,
591
- s,
592
- l,
593
- R(t.messages, t.tools)
594
- );
595
- for (const m of p)
596
- await this.evaluateCap(m, f, t.executionContext, a, l);
597
- }
598
- // ── Enforcement: pre:tool_call ────────────────────────────────────────────
599
- async enforcePreTool(t) {
600
- const { toolName: e, callId: o, executionContext: s } = t, {
601
- caps: n,
602
- mode: i,
603
- scope: r
604
- } = this.resolveCaps(s.agentName, "", e), a = this.accumulators.get(s.taskId);
605
- if (!a)
606
- return;
607
- const l = n.filter((d) => d.field !== "context");
608
- if (l.length === 0)
609
- return;
610
- const p = a.toolCalls.get(e) ?? 0, f = this.buildSnapshot(
611
- l,
612
- a,
613
- s.taskId,
614
- s.sessionId,
615
- s.agentName,
616
- r
617
- );
618
- for (const d of l) {
619
- const k = this.resolveCapLimit(d, s), m = d.field === "toolCalls" ? p : this.getSnapshotValue(f, d, r);
620
- if (m >= k) {
621
- const h = d.message ?? `tool "${e}": ${d.field} limit is ${k}, current value is ${Math.round(m)}`;
622
- throw (d.mode ?? i ?? "warning") === "warning" ? (await this.emitBudgetEvent(
623
- "budget:warning",
624
- s,
625
- d,
626
- m,
627
- k,
628
- h
629
- ), $(e, o, h)) : (await this.emitBudgetEvent(
630
- "budget:block",
631
- s,
632
- d,
633
- m,
634
- k,
635
- h
636
- ), v(
637
- `tool:${e}:${d.field}`,
638
- k,
639
- m,
640
- d.message
641
- ));
642
- }
643
- }
644
- a.toolCalls.set(e, p + 1);
645
- }
646
- // ── Enforcement: transform:tool_result (response size) ────────────────────
647
- enforceResponseSize(t) {
648
- const { toolName: e, result: o } = t;
649
- if (typeof o != "object" || o === null)
650
- return;
651
- const { caps: s, mode: n } = this.resolveCaps("", "", e), i = s.filter((p) => p.field === "responseSize");
652
- if (i.length === 0)
653
- return;
654
- const r = o, a = r.content;
655
- if (!Array.isArray(a))
656
- return;
657
- let l = 0;
658
- for (const p of a)
659
- if (typeof p == "object" && p !== null) {
660
- const f = p;
661
- f.type === "text" && (l += (f.text ?? "").length);
662
- }
663
- for (const p of i) {
664
- const f = this.resolveCapLimit(p, t.executionContext);
665
- if (l <= f)
666
- continue;
667
- if ((p.mode ?? n ?? "warning") === "block")
668
- r.content = [
669
- {
670
- type: "text",
671
- text: p.message ?? `Response size (${l} chars) exceeds limit of ${f}. Use offset/limit or shell paging tools instead.`
672
- }
673
- ], t.isError = !0;
674
- else {
675
- let k = f;
676
- const m = [];
677
- for (const h of a) {
678
- if (typeof h != "object" || h === null) {
679
- m.push(h);
680
- continue;
681
- }
682
- const C = h;
683
- if (C.type !== "text") {
684
- m.push(h);
685
- continue;
686
- }
687
- const T = C.text ?? "";
688
- if (T.length <= k)
689
- m.push(h), k -= T.length;
690
- else {
691
- m.push({ type: "text", text: T.slice(0, k) });
692
- break;
693
- }
694
- }
695
- m.push({
696
- type: "text",
697
- text: `
698
-
699
- [truncated: response was ${l} chars, limited to ${p.maximum}. ${p.message ?? "Use offset/limit or shell paging tools for full content."}]`
700
- }), r.content = m;
701
- }
702
- break;
703
- }
704
- }
705
- }
706
- const j = ({ db: c, config: t }) => {
707
- var r, a, l, p;
708
- const e = I(t), o = ((r = e.defaults) == null ? void 0 : r.costPerInputToken) ?? 0, s = ((a = e.defaults) == null ? void 0 : a.costPerOutputToken) ?? 0, n = ((l = e.defaults) == null ? void 0 : l.costPerCacheReadToken) ?? o, i = ((p = e.defaults) == null ? void 0 : p.costPerCacheWriteToken) ?? o;
709
- return new L(c, e, o, s, n, i);
710
- };
711
- export {
712
- N as configSchema,
713
- j as createPlugin,
714
- j as default,
715
- _ as pluginConfigSchema
370
+ WHERE created_at >= ?${e}`).get(...t);
371
+ }
372
+ return o?.total ?? 0;
373
+ } catch {
374
+ return 0;
375
+ }
376
+ }
377
+ buildSnapshot(e, t, r, i, a, o, s = 0) {
378
+ let c = {};
379
+ c.inputTokens = t.inputTokens, c.outputTokens = t.outputTokens, c.calls = t.modelCalls, c.wallClock = Date.now() - t.startedAtMs, c.modelMs = t.totalModelMs, c.context = Math.max(t.peakContextTokens, s), c.errors = t.errors, c.consecutiveErrors = t.consecutiveErrors, c.cost = t.uncachedInputTokens * this.costPerInput + t.cacheReadTokens * this.costPerCacheRead + t.cacheCreationTokens * this.costPerCacheWrite + t.outputTokens * this.costPerOutput;
380
+ let l = /* @__PURE__ */ new Set(), u = /* @__PURE__ */ new Map();
381
+ for (let t of e) {
382
+ let e = t.scope ?? o ?? "task";
383
+ if (e !== "task" && l.add(e), t.window) {
384
+ let r = `${e}:${t.window}`;
385
+ u.has(r) || u.set(r, {
386
+ scope: e,
387
+ windowMs: n(t.window)
388
+ });
389
+ }
390
+ }
391
+ for (let e of l) {
392
+ let t = this.queryScopeTotals(r, i, a, e);
393
+ c[`${e}:inputTokens`] = t.inputTokens, c[`${e}:outputTokens`] = t.outputTokens, c[`${e}:calls`] = t.modelCalls, c[`${e}:context`] = Math.max(t.peakContextTokens, s);
394
+ }
395
+ for (let [e, { scope: t, windowMs: n }] of u) {
396
+ let o = "";
397
+ t === "session" ? o = i ?? "" : t === "agent" && (o = a ?? ""), c[e] = this.queryWindowTokens(t, o, n, r);
398
+ }
399
+ return c;
400
+ }
401
+ getSnapshotValue(e, t, n) {
402
+ let r = t.scope ?? n ?? "task", i;
403
+ i = t.window || r === "task" ? "" : `${r}:`;
404
+ let a;
405
+ switch (t.field) {
406
+ case "inputTokens":
407
+ a = e[`${i}inputTokens`] ?? e.inputTokens;
408
+ break;
409
+ case "outputTokens":
410
+ a = e[`${i}outputTokens`] ?? e.outputTokens;
411
+ break;
412
+ case "context":
413
+ a = e[`${i}context`] ?? e.context;
414
+ break;
415
+ case "calls":
416
+ a = e[`${i}calls`] ?? e.calls;
417
+ break;
418
+ case "wallClock":
419
+ a = e.wallClock;
420
+ break;
421
+ case "modelMs":
422
+ a = e.modelMs;
423
+ break;
424
+ case "cost":
425
+ a = e.cost;
426
+ break;
427
+ case "toolCalls":
428
+ a = 0;
429
+ break;
430
+ case "errors":
431
+ a = e.errors;
432
+ break;
433
+ case "consecutiveErrors":
434
+ a = e.consecutiveErrors;
435
+ break;
436
+ default: a = 0;
437
+ }
438
+ return t.window && (a += e[`${r}:${t.window}`] ?? 0), a;
439
+ }
440
+ async emitBudgetEvent(e, t, n, r, i, a) {
441
+ this.hooks && await this.hooks.emit(e, {
442
+ executionContext: t,
443
+ field: n.field,
444
+ maximum: i,
445
+ current: r,
446
+ message: a ?? n.message ?? `${n.field} limit is ${i}, current value is ${Math.round(r)}`
447
+ });
448
+ }
449
+ resolveCapLimit(e, n) {
450
+ if (e.maximum !== void 0) return e.maximum;
451
+ let r = n.agentDefinition.provider;
452
+ return Math.floor(t(r.model) * (e.contextWindowFraction ?? 0));
453
+ }
454
+ async evaluateCap(e, t, n, r, i) {
455
+ let a = this.resolveCapLimit(e, n), o = this.getSnapshotValue(t, e, i);
456
+ if (!(o < a)) {
457
+ if ((e.mode ?? r ?? "warning") === "warning") {
458
+ await this.emitBudgetEvent("budget:warning", n, e, o, a);
459
+ return;
460
+ }
461
+ throw await this.emitBudgetEvent("budget:block", n, e, o, a), h(e.field, a, o, e.message);
462
+ }
463
+ }
464
+ async enforcePreModel(e) {
465
+ let { taskId: t, sessionId: n, agentName: r } = e.executionContext, a = e.executionContext.agentDefinition?.provider?.type ?? "unknown", o = this.accumulators.get(t);
466
+ if (!o) return;
467
+ let { caps: s, mode: c, scope: l } = this.resolveCaps(r, a), u = s.filter((e) => !i.has(e.field));
468
+ if (u.length === 0) return;
469
+ let d = this.buildSnapshot(u, o, t, n, r, l, _(e.messages, e.tools));
470
+ for (let t of u) await this.evaluateCap(t, d, e.executionContext, c, l);
471
+ }
472
+ async enforcePreTool(e) {
473
+ let { toolName: t, callId: n, executionContext: r } = e, { caps: i, mode: a, scope: o } = this.resolveCaps(r.agentName, "", t), s = this.accumulators.get(r.taskId);
474
+ if (!s) return;
475
+ let c = i.filter((e) => e.field !== "context");
476
+ if (c.length === 0) return;
477
+ let l = s.toolCalls.get(t) ?? 0, u = this.buildSnapshot(c, s, r.taskId, r.sessionId, r.agentName, o);
478
+ for (let e of c) {
479
+ let i = this.resolveCapLimit(e, r), s = e.field === "toolCalls" ? l : this.getSnapshotValue(u, e, o);
480
+ if (s >= i) {
481
+ let o = e.message ?? `tool "${t}": ${e.field} limit is ${i}, current value is ${Math.round(s)}`;
482
+ throw (e.mode ?? a ?? "warning") === "warning" ? (await this.emitBudgetEvent("budget:warning", r, e, s, i, o), g(t, n, o)) : (await this.emitBudgetEvent("budget:block", r, e, s, i, o), h(`tool:${t}:${e.field}`, i, s, e.message));
483
+ }
484
+ }
485
+ s.toolCalls.set(t, l + 1);
486
+ }
487
+ enforceResponseSize(e) {
488
+ let { toolName: t, result: n } = e;
489
+ if (typeof n != "object" || !n) return;
490
+ let { caps: r, mode: i } = this.resolveCaps("", "", t), a = r.filter((e) => e.field === "responseSize");
491
+ if (a.length === 0) return;
492
+ let o = n, s = o.content;
493
+ if (!Array.isArray(s)) return;
494
+ let c = 0;
495
+ for (let e of s) if (typeof e == "object" && e) {
496
+ let t = e;
497
+ t.type === "text" && (c += (t.text ?? "").length);
498
+ }
499
+ for (let t of a) {
500
+ let n = this.resolveCapLimit(t, e.executionContext);
501
+ if (!(c <= n)) {
502
+ if ((t.mode ?? i ?? "warning") === "block") o.content = [{
503
+ type: "text",
504
+ text: t.message ?? `Response size (${c} chars) exceeds limit of ${n}. Use offset/limit or shell paging tools instead.`
505
+ }], e.isError = !0;
506
+ else {
507
+ let e = n, r = [];
508
+ for (let t of s) {
509
+ if (typeof t != "object" || !t) {
510
+ r.push(t);
511
+ continue;
512
+ }
513
+ let n = t;
514
+ if (n.type !== "text") {
515
+ r.push(t);
516
+ continue;
517
+ }
518
+ let i = n.text ?? "";
519
+ if (i.length <= e) r.push(t), e -= i.length;
520
+ else {
521
+ r.push({
522
+ type: "text",
523
+ text: i.slice(0, e)
524
+ });
525
+ break;
526
+ }
527
+ }
528
+ r.push({
529
+ type: "text",
530
+ text: `\n\n[truncated: response was ${c} chars, limited to ${t.maximum}. ${t.message ?? "Use offset/limit or shell paging tools for full content."}]`
531
+ }), o.content = r;
532
+ }
533
+ break;
534
+ }
535
+ }
536
+ }
537
+ }, y = ({ db: e, config: t }) => {
538
+ let n = m(t), r = n.defaults?.costPerInputToken ?? 0;
539
+ return new v(e, n, r, n.defaults?.costPerOutputToken ?? 0, n.defaults?.costPerCacheReadToken ?? r, n.defaults?.costPerCacheWriteToken ?? r);
716
540
  };
541
+ //#endregion
542
+ export { l as configSchema, y as createPlugin, y as default, c as pluginConfigSchema };