@theokit/sdk-budget 0.1.0

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/index.cjs ADDED
@@ -0,0 +1,430 @@
1
+ 'use strict';
2
+
3
+ var sdk = require('@theokit/sdk');
4
+
5
+ // src/internal/calendar-window.ts
6
+ function startOfDayUtc(now = /* @__PURE__ */ new Date()) {
7
+ return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
8
+ }
9
+ function startOfWeekUtc(now = /* @__PURE__ */ new Date()) {
10
+ const dayOfWeek = now.getUTCDay();
11
+ const daysSinceMonday = (dayOfWeek + 6) % 7;
12
+ const start = startOfDayUtc(now);
13
+ start.setUTCDate(start.getUTCDate() - daysSinceMonday);
14
+ return start;
15
+ }
16
+ var MS_PER_HOUR = 60 * 60 * 1e3;
17
+ var MS_PER_DAY = 24 * MS_PER_HOUR;
18
+ function windowStartMs(window, now = /* @__PURE__ */ new Date()) {
19
+ switch (window) {
20
+ case "1h":
21
+ return now.getTime() - MS_PER_HOUR;
22
+ case "1d":
23
+ return startOfDayUtc(now).getTime();
24
+ case "1w":
25
+ return startOfWeekUtc(now).getTime();
26
+ case "30d":
27
+ return now.getTime() - 30 * MS_PER_DAY;
28
+ case "365d":
29
+ return now.getTime() - 365 * MS_PER_DAY;
30
+ default: {
31
+ const _exhaustive = window;
32
+ throw new Error(`unreachable window: ${_exhaustive}`);
33
+ }
34
+ }
35
+ }
36
+ var MS_PER_YEAR = 365 * 24 * 60 * 60 * 1e3;
37
+ var GC_INTERVAL_MS = 5 * 60 * 1e3;
38
+ var GC_LOGS_THRESHOLD = 1e4;
39
+ var state = {
40
+ logs: /* @__PURE__ */ new Map(),
41
+ lastGcAt: Date.now()
42
+ };
43
+ var MUTEX_KEY = "budget-ledger";
44
+ function shouldGc(now) {
45
+ if (now - state.lastGcAt < GC_INTERVAL_MS) return false;
46
+ let totalLogs = 0;
47
+ for (const arr of state.logs.values()) totalLogs += arr.length;
48
+ return totalLogs > GC_LOGS_THRESHOLD;
49
+ }
50
+ function gcOlderThanOneYear(now) {
51
+ const cutoff = now - MS_PER_YEAR;
52
+ for (const [name, arr] of state.logs.entries()) {
53
+ const kept = arr.filter((l) => l.timestamp >= cutoff);
54
+ if (kept.length === 0) state.logs.delete(name);
55
+ else state.logs.set(name, kept);
56
+ }
57
+ state.lastGcAt = now;
58
+ }
59
+ async function charge(name, amountUsd) {
60
+ if (amountUsd <= 0) return;
61
+ await sdk.withCwdMutex(MUTEX_KEY, async () => {
62
+ const now = Date.now();
63
+ const list = state.logs.get(name) ?? [];
64
+ list.push({ timestamp: now, amountUsd });
65
+ state.logs.set(name, list);
66
+ if (shouldGc(now)) gcOlderThanOneYear(now);
67
+ });
68
+ }
69
+ function spentIn(name, window, now = /* @__PURE__ */ new Date()) {
70
+ const arr = state.logs.get(name);
71
+ if (arr === void 0) return 0;
72
+ const sinceMs = windowStartMs(window, now);
73
+ let total = 0;
74
+ for (const log of arr) {
75
+ if (log.timestamp >= sinceMs) total += log.amountUsd;
76
+ }
77
+ return total;
78
+ }
79
+ var NAME_GRAMMAR = /^[a-z0-9][a-z0-9_-]*$/;
80
+ var registry = /* @__PURE__ */ new Map();
81
+ function validateBudgetName(name) {
82
+ if (typeof name !== "string" || name.length === 0) {
83
+ throw new sdk.ConfigurationError("Budget name must be a non-empty string", {
84
+ code: "invalid_budget_name"
85
+ });
86
+ }
87
+ if (!NAME_GRAMMAR.test(name)) {
88
+ throw new sdk.ConfigurationError(
89
+ `Budget name "${name}" must match ^[a-z0-9][a-z0-9_-]*$ (lowercase + dash/underscore, start alphanumeric)`,
90
+ { code: "invalid_budget_name" }
91
+ );
92
+ }
93
+ }
94
+ function createBudget(opts) {
95
+ validateBudgetName(opts.name);
96
+ if (registry.has(opts.name)) {
97
+ throw new sdk.ConfigurationError(`Budget "${opts.name}" already exists`, {
98
+ code: "invalid_budget_name"
99
+ });
100
+ }
101
+ registry.set(opts.name, opts);
102
+ return buildHandle(opts);
103
+ }
104
+ function getBudget(name) {
105
+ const opts = registry.get(name);
106
+ if (opts === void 0) return void 0;
107
+ return buildHandle(opts);
108
+ }
109
+ function listBudgets() {
110
+ return [...registry.values()].map(buildHandle);
111
+ }
112
+ function deleteBudget(name) {
113
+ return registry.delete(name);
114
+ }
115
+ function snapshotAll() {
116
+ const result = [];
117
+ for (const opts of registry.values()) {
118
+ for (const lim of opts.limits) {
119
+ const spent = spentIn(opts.name, lim.window);
120
+ result.push({
121
+ name: opts.name,
122
+ window: lim.window,
123
+ spentUsd: spent,
124
+ limitUsd: lim.limitUsd,
125
+ ratio: lim.limitUsd > 0 ? spent / lim.limitUsd : 0
126
+ });
127
+ }
128
+ }
129
+ return result;
130
+ }
131
+ function getBudgetOptionsRaw(name) {
132
+ return registry.get(name);
133
+ }
134
+ function defaultMode(opts) {
135
+ return opts.mode ?? "warn";
136
+ }
137
+ function buildHandle(opts) {
138
+ return {
139
+ name: opts.name,
140
+ mode: defaultMode(opts),
141
+ scope: opts.scope,
142
+ limits: opts.limits,
143
+ spentIn: (window) => spentIn(opts.name, window),
144
+ remainingIn: (window) => {
145
+ const lim = opts.limits.find((l) => l.window === window);
146
+ if (lim === void 0) return Number.POSITIVE_INFINITY;
147
+ return Math.max(0, lim.limitUsd - spentIn(opts.name, window));
148
+ }
149
+ };
150
+ }
151
+
152
+ // src/internal/enforcement.ts
153
+ var THRESHOLDS = [0.8, 0.95];
154
+ function preflightCheck(name, estimatedUsd) {
155
+ const opts = getBudgetOptionsRaw(name);
156
+ if (opts === void 0) return;
157
+ if (defaultMode(opts) !== "block") return;
158
+ for (const lim of opts.limits) {
159
+ const currentSpent = spentIn(opts.name, lim.window);
160
+ if (currentSpent + estimatedUsd > lim.limitUsd) {
161
+ throw new sdk.BudgetExceededError({
162
+ budgetName: opts.name,
163
+ window: lim.window,
164
+ spentUsd: currentSpent + estimatedUsd,
165
+ limitUsd: lim.limitUsd,
166
+ mode: "block"
167
+ });
168
+ }
169
+ }
170
+ }
171
+ async function chargeAndCheckThresholds(name, actualUsd) {
172
+ const opts = getBudgetOptionsRaw(name);
173
+ if (opts === void 0) {
174
+ process.stderr.write(
175
+ `[budget] charge for deleted budget "${name}" is a no-op (was the budget removed during in-flight send?)
176
+ `
177
+ );
178
+ return;
179
+ }
180
+ const mode = defaultMode(opts);
181
+ await charge(opts.name, actualUsd);
182
+ if (mode === "audit") return;
183
+ await dispatchCallbacksFor(opts, mode);
184
+ }
185
+ async function dispatchCallbacksFor(opts, mode) {
186
+ for (const lim of opts.limits) {
187
+ const spent = spentIn(opts.name, lim.window);
188
+ if (lim.limitUsd <= 0) {
189
+ if (spent > 0) await dispatchExceed(opts, lim.window, spent, lim.limitUsd, mode);
190
+ continue;
191
+ }
192
+ const ratio = spent / lim.limitUsd;
193
+ if (ratio >= 1) {
194
+ await dispatchExceed(opts, lim.window, spent, lim.limitUsd, mode);
195
+ } else {
196
+ for (const t of [...THRESHOLDS].reverse()) {
197
+ if (ratio >= t) {
198
+ await dispatchThreshold(opts, lim.window, spent, lim.limitUsd, t);
199
+ break;
200
+ }
201
+ }
202
+ }
203
+ }
204
+ }
205
+ async function dispatchThreshold(opts, window, spentUsd, limitUsd, threshold) {
206
+ if (opts.onThreshold === void 0) return;
207
+ try {
208
+ await opts.onThreshold({
209
+ budgetName: opts.name,
210
+ window,
211
+ threshold,
212
+ spentUsd,
213
+ limitUsd
214
+ });
215
+ } catch (err) {
216
+ const msg = err instanceof Error ? err.message : String(err);
217
+ process.stderr.write(`[budget] onThreshold callback threw: ${msg}
218
+ `);
219
+ }
220
+ }
221
+ async function dispatchExceed(opts, window, spentUsd, limitUsd, mode) {
222
+ if (opts.onExceed === void 0) {
223
+ if (mode === "warn") {
224
+ process.stderr.write(
225
+ `[budget] "${opts.name}" exceeded ${window} limit: $${spentUsd.toFixed(4)} > $${limitUsd.toFixed(4)}
226
+ `
227
+ );
228
+ }
229
+ return;
230
+ }
231
+ try {
232
+ await opts.onExceed({
233
+ budgetName: opts.name,
234
+ window,
235
+ spentUsd,
236
+ limitUsd,
237
+ mode
238
+ });
239
+ } catch (err) {
240
+ const msg = err instanceof Error ? err.message : String(err);
241
+ process.stderr.write(`[budget] onExceed callback threw: ${msg}
242
+ `);
243
+ }
244
+ }
245
+
246
+ // src/internal/normalize-usage.ts
247
+ function int(v) {
248
+ if (typeof v === "number") return Number.isFinite(v) ? Math.max(0, Math.trunc(v)) : 0;
249
+ if (typeof v === "string") {
250
+ const n = Number.parseInt(v, 10);
251
+ return Number.isFinite(n) ? Math.max(0, n) : 0;
252
+ }
253
+ return 0;
254
+ }
255
+ function buildTotal(buckets) {
256
+ return buckets.inputTokens + buckets.outputTokens + buckets.cacheReadTokens + buckets.cacheWriteTokens;
257
+ }
258
+ function omitUndefined(usage) {
259
+ return {
260
+ inputTokens: usage.inputTokens,
261
+ outputTokens: usage.outputTokens,
262
+ ...usage.cacheReadTokens > 0 ? { cacheReadTokens: usage.cacheReadTokens } : {},
263
+ ...usage.cacheWriteTokens > 0 ? { cacheWriteTokens: usage.cacheWriteTokens } : {},
264
+ ...usage.reasoningTokens > 0 ? { reasoningTokens: usage.reasoningTokens } : {},
265
+ totalTokens: usage.totalTokens
266
+ };
267
+ }
268
+ function inferApiMode(provider) {
269
+ const p = provider.toLowerCase();
270
+ if (p === "anthropic" || p === "claude" || p === "bedrock_anthropic") {
271
+ return "anthropic_messages";
272
+ }
273
+ if (p === "openai-codex" || p === "codex") return "openai_responses";
274
+ return "openai_chat_completions";
275
+ }
276
+ function normalizeUsage(rawUsage, opts) {
277
+ if (rawUsage === null || rawUsage === void 0) {
278
+ return { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
279
+ }
280
+ if (typeof rawUsage !== "object") {
281
+ return { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
282
+ }
283
+ const raw = rawUsage;
284
+ const mode = opts.apiMode ?? inferApiMode(opts.provider);
285
+ if (mode === "anthropic_messages") return normalizeAnthropic(raw);
286
+ if (mode === "openai_responses") return normalizeOpenAIResponses(raw);
287
+ return normalizeOpenAIChat(raw);
288
+ }
289
+ function normalizeAnthropic(raw) {
290
+ const inputTokens = int(raw.input_tokens);
291
+ const outputTokens = int(raw.output_tokens);
292
+ const cacheReadTokens = int(raw.cache_read_input_tokens);
293
+ const cacheWriteTokens = int(raw.cache_creation_input_tokens);
294
+ const usage = {
295
+ inputTokens,
296
+ outputTokens,
297
+ cacheReadTokens,
298
+ cacheWriteTokens,
299
+ reasoningTokens: 0,
300
+ totalTokens: buildTotal({ inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens })
301
+ };
302
+ return omitUndefined(usage);
303
+ }
304
+ function normalizeOpenAIResponses(raw) {
305
+ const inputTotal = int(raw.input_tokens);
306
+ const outputTokens = int(raw.output_tokens);
307
+ const inputDetails = raw.input_tokens_details ?? {};
308
+ const outputDetails = raw.output_tokens_details ?? {};
309
+ const cacheReadTokens = int(inputDetails.cached_tokens);
310
+ const cacheWriteTokens = int(inputDetails.cache_creation_tokens);
311
+ const reasoningTokens = int(outputDetails.reasoning_tokens);
312
+ const inputTokens = Math.max(0, inputTotal - cacheReadTokens - cacheWriteTokens);
313
+ const usage = {
314
+ inputTokens,
315
+ outputTokens,
316
+ cacheReadTokens,
317
+ cacheWriteTokens,
318
+ reasoningTokens,
319
+ totalTokens: buildTotal({ inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens })
320
+ };
321
+ return omitUndefined(usage);
322
+ }
323
+ function normalizeOpenAIChat(raw) {
324
+ const promptTotal = int(raw.prompt_tokens);
325
+ const outputTokens = int(raw.completion_tokens);
326
+ const promptDetails = raw.prompt_tokens_details ?? {};
327
+ const completionDetails = raw.completion_tokens_details ?? {};
328
+ const cacheReadTokens = int(promptDetails.cached_tokens) || int(raw.cache_read_input_tokens);
329
+ const cacheWriteTokens = int(promptDetails.cache_write_tokens) || int(raw.cache_creation_input_tokens);
330
+ const reasoningTokens = int(completionDetails.reasoning_tokens);
331
+ const inputTokens = Math.max(0, promptTotal - cacheReadTokens - cacheWriteTokens);
332
+ const usage = {
333
+ inputTokens,
334
+ outputTokens,
335
+ cacheReadTokens,
336
+ cacheWriteTokens,
337
+ reasoningTokens,
338
+ totalTokens: buildTotal({ inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens })
339
+ };
340
+ return omitUndefined(usage);
341
+ }
342
+
343
+ // src/usd-pricing.ts
344
+ var BUILTIN_PRICING = Object.freeze({
345
+ // OpenAI / OpenRouter aliases
346
+ "openai/gpt-4o": { inputPerMillionUsd: 2.5, outputPerMillionUsd: 10 },
347
+ "openai/gpt-4o-mini": { inputPerMillionUsd: 0.15, outputPerMillionUsd: 0.6 },
348
+ "openai/gpt-4-turbo": { inputPerMillionUsd: 10, outputPerMillionUsd: 30 },
349
+ "openai/gpt-3.5-turbo": { inputPerMillionUsd: 0.5, outputPerMillionUsd: 1.5 },
350
+ // Anthropic / Claude
351
+ "anthropic/claude-3-5-sonnet-latest": { inputPerMillionUsd: 3, outputPerMillionUsd: 15 },
352
+ "anthropic/claude-3-5-haiku-latest": { inputPerMillionUsd: 0.8, outputPerMillionUsd: 4 },
353
+ "anthropic/claude-3-opus-latest": { inputPerMillionUsd: 15, outputPerMillionUsd: 75 },
354
+ // Google
355
+ "google/gemini-1.5-pro": { inputPerMillionUsd: 1.25, outputPerMillionUsd: 5 },
356
+ "google/gemini-1.5-flash": { inputPerMillionUsd: 0.075, outputPerMillionUsd: 0.3 }
357
+ });
358
+ function computeUsdCost(pricing, model, type, tokens) {
359
+ const entry = pricing[model];
360
+ if (entry === void 0) return 0;
361
+ const ratePerMillion = type === "input" ? entry.inputPerMillionUsd : entry.outputPerMillionUsd;
362
+ if (!Number.isFinite(tokens) || tokens <= 0) return 0;
363
+ return tokens / 1e6 * ratePerMillion;
364
+ }
365
+
366
+ // src/usd-budget-tracker.ts
367
+ function createUsdBudgetTracker(options = {}) {
368
+ let totalTokens = 0;
369
+ let iterations = 0;
370
+ let totalUsd = 0;
371
+ const maxTokens = options.maxTokens;
372
+ const maxUsd = options.maxUsd;
373
+ const pricing = options.pricing !== void 0 ? { ...BUILTIN_PRICING, ...options.pricing } : BUILTIN_PRICING;
374
+ return {
375
+ track(event) {
376
+ const t = Number.isFinite(event.tokens) && event.tokens > 0 ? event.tokens : 0;
377
+ if (t === 0) return;
378
+ totalTokens += t;
379
+ totalUsd += computeUsdCost(pricing, event.model, event.type, t);
380
+ },
381
+ check() {
382
+ if (maxUsd !== void 0 && totalUsd >= maxUsd) {
383
+ return {
384
+ allowed: false,
385
+ reason: "cost_limit",
386
+ detail: `USD ${totalUsd.toFixed(6)} >= maxUsd ${maxUsd}`
387
+ };
388
+ }
389
+ if (maxTokens !== void 0 && totalTokens >= maxTokens) {
390
+ return {
391
+ allowed: false,
392
+ reason: "token_limit",
393
+ detail: `${totalTokens} >= maxTokens ${maxTokens}`
394
+ };
395
+ }
396
+ return { allowed: true };
397
+ },
398
+ getTotal() {
399
+ return { tokens: totalTokens, iterations };
400
+ },
401
+ getTotalUsd() {
402
+ return totalUsd;
403
+ },
404
+ nextIteration() {
405
+ iterations += 1;
406
+ }
407
+ };
408
+ }
409
+
410
+ exports.BUILTIN_PRICING = BUILTIN_PRICING;
411
+ exports.charge = charge;
412
+ exports.chargeAndCheckThresholds = chargeAndCheckThresholds;
413
+ exports.computeUsdCost = computeUsdCost;
414
+ exports.createBudget = createBudget;
415
+ exports.createUsdBudgetTracker = createUsdBudgetTracker;
416
+ exports.defaultMode = defaultMode;
417
+ exports.deleteBudget = deleteBudget;
418
+ exports.getBudget = getBudget;
419
+ exports.getBudgetOptionsRaw = getBudgetOptionsRaw;
420
+ exports.inferApiMode = inferApiMode;
421
+ exports.listBudgets = listBudgets;
422
+ exports.normalizeUsage = normalizeUsage;
423
+ exports.preflightCheck = preflightCheck;
424
+ exports.snapshotAll = snapshotAll;
425
+ exports.spentIn = spentIn;
426
+ exports.startOfDayUtc = startOfDayUtc;
427
+ exports.startOfWeekUtc = startOfWeekUtc;
428
+ exports.windowStartMs = windowStartMs;
429
+ //# sourceMappingURL=index.cjs.map
430
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/calendar-window.ts","../src/internal/ledger.ts","../src/internal/registry.ts","../src/internal/enforcement.ts","../src/internal/normalize-usage.ts","../src/usd-pricing.ts","../src/usd-budget-tracker.ts"],"names":["withCwdMutex","ConfigurationError","BudgetExceededError"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * UTC-aligned calendar window helpers (ADR D382).\n *\n * - `1h` — relative (now - 1 hour).\n * - `1d` — UTC midnight (current UTC day).\n * - `1w` — UTC monday 00:00:00 (current UTC week, Monday is week start).\n * - `30d` — relative 30 days.\n * - `365d` — relative 365 days.\n *\n * `1d` and `1w` are calendar-aligned because users expect \"1 USD per day\"\n * = \"since midnight UTC\", not a rolling 24h.\n * `30d`/`365d` are relative because nobody expects \"since the 1st\".\n *\n * @internal\n */\n\nimport type { BudgetWindow } from \"@theokit/sdk\";\n\nexport function startOfDayUtc(now: Date = new Date()): Date {\n return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));\n}\n\nexport function startOfWeekUtc(now: Date = new Date()): Date {\n // ISO 8601 week starts on Monday. getUTCDay() returns 0 (Sun) .. 6 (Sat).\n const dayOfWeek = now.getUTCDay();\n const daysSinceMonday = (dayOfWeek + 6) % 7; // Mon=0, Sun=6\n const start = startOfDayUtc(now);\n start.setUTCDate(start.getUTCDate() - daysSinceMonday);\n return start;\n}\n\nconst MS_PER_HOUR = 60 * 60 * 1000;\nconst MS_PER_DAY = 24 * MS_PER_HOUR;\n\n/** Returns the inclusive start timestamp (ms) for the given window relative to `now`. */\nexport function windowStartMs(window: BudgetWindow, now: Date = new Date()): number {\n switch (window) {\n case \"1h\":\n return now.getTime() - MS_PER_HOUR;\n case \"1d\":\n return startOfDayUtc(now).getTime();\n case \"1w\":\n return startOfWeekUtc(now).getTime();\n case \"30d\":\n return now.getTime() - 30 * MS_PER_DAY;\n case \"365d\":\n return now.getTime() - 365 * MS_PER_DAY;\n default: {\n const _exhaustive: never = window;\n throw new Error(`unreachable window: ${_exhaustive as string}`);\n }\n }\n}\n","/**\n * In-process Budget ledger (ADR D385).\n *\n * Singleton mutex-protected. Stores per-budget ChargeLog[] arrays;\n * `spentIn(window)` filters by timestamp.\n *\n * EC-6: GC eviction runs DENTRO do mesmo mutex que charge — sem race.\n * EC-9: charge() é called inside same critical section as preflight\n * check by Budget enforcement.\n *\n * Persistence cross-restart: deferred to v0.2 (JsonFile pattern).\n *\n * @internal\n */\n\nimport { type BudgetWindow, withCwdMutex } from \"@theokit/sdk\";\nimport { windowStartMs } from \"./calendar-window.js\";\n\ninterface ChargeLog {\n timestamp: number;\n amountUsd: number;\n}\n\nconst MS_PER_YEAR = 365 * 24 * 60 * 60 * 1000;\nconst GC_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes\nconst GC_LOGS_THRESHOLD = 10_000;\n\ninterface LedgerState {\n readonly logs: Map<string, ChargeLog[]>;\n lastGcAt: number;\n}\n\nlet state: LedgerState = {\n logs: new Map(),\n lastGcAt: Date.now(),\n};\n\nconst MUTEX_KEY = \"budget-ledger\";\n\nfunction shouldGc(now: number): boolean {\n if (now - state.lastGcAt < GC_INTERVAL_MS) return false;\n let totalLogs = 0;\n for (const arr of state.logs.values()) totalLogs += arr.length;\n return totalLogs > GC_LOGS_THRESHOLD;\n}\n\nfunction gcOlderThanOneYear(now: number): void {\n const cutoff = now - MS_PER_YEAR;\n for (const [name, arr] of state.logs.entries()) {\n const kept = arr.filter((l) => l.timestamp >= cutoff);\n if (kept.length === 0) state.logs.delete(name);\n else state.logs.set(name, kept);\n }\n state.lastGcAt = now;\n}\n\n/** Charge a budget. Idempotent across concurrent calls via withCwdMutex. */\nexport async function charge(name: string, amountUsd: number): Promise<void> {\n if (amountUsd <= 0) return;\n await withCwdMutex(MUTEX_KEY, async () => {\n const now = Date.now();\n const list = state.logs.get(name) ?? [];\n list.push({ timestamp: now, amountUsd });\n state.logs.set(name, list);\n if (shouldGc(now)) gcOlderThanOneYear(now);\n });\n}\n\n/** Return total spend in the given window for `name`. Snapshot read (no mutex needed). */\nexport function spentIn(name: string, window: BudgetWindow, now: Date = new Date()): number {\n const arr = state.logs.get(name);\n if (arr === undefined) return 0;\n const sinceMs = windowStartMs(window, now);\n let total = 0;\n for (const log of arr) {\n if (log.timestamp >= sinceMs) total += log.amountUsd;\n }\n return total;\n}\n\n/** Diagnostic — total logs across all budgets. */\nfunction __getLogCountForTests(name?: string): number {\n if (name !== undefined) return state.logs.get(name)?.length ?? 0;\n let total = 0;\n for (const arr of state.logs.values()) total += arr.length;\n return total;\n}\n\nfunction __resetLedgerForTests(): void {\n state = { logs: new Map(), lastGcAt: Date.now() };\n}\n\n/** Force GC manually (tests only). */\nasync function __evictNowForTests(): Promise<void> {\n await withCwdMutex(MUTEX_KEY, async () => {\n gcOlderThanOneYear(Date.now());\n });\n}\n\n/** Force-insert a log at a specific timestamp (tests only). */\nfunction __injectLogForTests(name: string, timestamp: number, amountUsd: number): void {\n const list = state.logs.get(name) ?? [];\n list.push({ timestamp, amountUsd });\n state.logs.set(name, list);\n}\n","/**\n * Internal Budget registry — keeps live `BudgetOptions` per name.\n * Singleton; no persistence (D385).\n *\n * @internal\n */\n\nimport {\n type BudgetHandle,\n type BudgetMode,\n type BudgetOptions,\n type BudgetSnapshot,\n type BudgetWindow,\n ConfigurationError,\n} from \"@theokit/sdk\";\nimport { spentIn } from \"./ledger.js\";\n\nconst NAME_GRAMMAR = /^[a-z0-9][a-z0-9_-]*$/;\n\nconst registry = new Map<string, BudgetOptions>();\n\nfunction __resetRegistryForTests(): void {\n registry.clear();\n}\n\nfunction validateBudgetName(name: string): void {\n if (typeof name !== \"string\" || name.length === 0) {\n throw new ConfigurationError(\"Budget name must be a non-empty string\", {\n code: \"invalid_budget_name\",\n });\n }\n if (!NAME_GRAMMAR.test(name)) {\n throw new ConfigurationError(\n `Budget name \"${name}\" must match ^[a-z0-9][a-z0-9_-]*$ (lowercase + dash/underscore, start alphanumeric)`,\n { code: \"invalid_budget_name\" },\n );\n }\n}\n\nexport function createBudget(opts: BudgetOptions): BudgetHandle {\n // EC-7: name validation\n validateBudgetName(opts.name);\n if (registry.has(opts.name)) {\n // EC-16: duplicate throws (vs Task.submit idempotent return)\n throw new ConfigurationError(`Budget \"${opts.name}\" already exists`, {\n code: \"invalid_budget_name\",\n });\n }\n registry.set(opts.name, opts);\n return buildHandle(opts);\n}\n\nexport function getBudget(name: string): BudgetHandle | undefined {\n const opts = registry.get(name);\n if (opts === undefined) return undefined;\n return buildHandle(opts);\n}\n\nexport function listBudgets(): readonly BudgetHandle[] {\n return [...registry.values()].map(buildHandle);\n}\n\nexport function deleteBudget(name: string): boolean {\n return registry.delete(name);\n}\n\nexport function snapshotAll(): readonly BudgetSnapshot[] {\n const result: BudgetSnapshot[] = [];\n for (const opts of registry.values()) {\n for (const lim of opts.limits) {\n const spent = spentIn(opts.name, lim.window);\n result.push({\n name: opts.name,\n window: lim.window,\n spentUsd: spent,\n limitUsd: lim.limitUsd,\n ratio: lim.limitUsd > 0 ? spent / lim.limitUsd : 0,\n });\n }\n }\n return result;\n}\n\nexport function getBudgetOptionsRaw(name: string): BudgetOptions | undefined {\n return registry.get(name);\n}\n\nexport function defaultMode(opts: BudgetOptions): BudgetMode {\n return opts.mode ?? \"warn\";\n}\n\nfunction buildHandle(opts: BudgetOptions): BudgetHandle {\n return {\n name: opts.name,\n mode: defaultMode(opts),\n scope: opts.scope,\n limits: opts.limits,\n spentIn: (window: BudgetWindow) => spentIn(opts.name, window),\n remainingIn: (window: BudgetWindow) => {\n const lim = opts.limits.find((l) => l.window === window);\n if (lim === undefined) return Number.POSITIVE_INFINITY;\n return Math.max(0, lim.limitUsd - spentIn(opts.name, window));\n },\n };\n}\n","/**\n * Budget enforcement (ADRs D383, D386, EC-7/8/9).\n *\n * - `preflightCheck(name, estimatedUsd)` — em `block` mode, throw\n * `BudgetExceededError` antes da LLM call se qualquer limit seria\n * excedido (EC-9 — caller chama dentro do mutex section).\n * - `chargeAndCheckThresholds(name, actualUsd)` — apply charge ao\n * ledger + invoca onThreshold/onExceed callbacks isolated em\n * try/catch (EC-8).\n *\n * @internal\n */\n\nimport { BudgetExceededError, type BudgetMode, type BudgetOptions } from \"@theokit/sdk\";\nimport { charge, spentIn } from \"./ledger.js\";\nimport { defaultMode, getBudgetOptionsRaw } from \"./registry.js\";\n\nconst THRESHOLDS = [0.8, 0.95] as const;\ntype Threshold = (typeof THRESHOLDS)[number];\n\n/**\n * Throws BudgetExceededError if `mode === \"block\"` and any limit\n * would be exceeded. No-op for audit/warn modes (post-charge checks\n * handle those).\n *\n * Caller invokes this BEFORE the LLM call.\n */\nexport function preflightCheck(name: string, estimatedUsd: number): void {\n const opts = getBudgetOptionsRaw(name);\n if (opts === undefined) return; // EC-20: budget deleted; charge becomes no-op\n if (defaultMode(opts) !== \"block\") return;\n for (const lim of opts.limits) {\n const currentSpent = spentIn(opts.name, lim.window);\n if (currentSpent + estimatedUsd > lim.limitUsd) {\n throw new BudgetExceededError({\n budgetName: opts.name,\n window: lim.window,\n spentUsd: currentSpent + estimatedUsd,\n limitUsd: lim.limitUsd,\n mode: \"block\",\n });\n }\n }\n}\n\n/**\n * Charge the budget + dispatch threshold/exceed callbacks (EC-8 isolated).\n *\n * - In `audit` mode: charge only, no callbacks.\n * - In `warn` mode: charge + onThreshold (80/95) + onExceed (100). No throw.\n * - In `block` mode: charge + onThreshold + onExceed. No throw post-call\n * (preflightCheck already prevented exceed for the upcoming call;\n * this protects against simultaneous-call races where multiple sends\n * each pass preflight independently — last one to charge may still\n * tip a limit. We document the case rather than retroactively throw).\n */\nexport async function chargeAndCheckThresholds(name: string, actualUsd: number): Promise<void> {\n const opts = getBudgetOptionsRaw(name);\n if (opts === undefined) {\n // EC-20: budget deleted during in-flight call. Charge is silent no-op.\n process.stderr.write(\n `[budget] charge for deleted budget \"${name}\" is a no-op (was the budget removed during in-flight send?)\\n`,\n );\n return;\n }\n const mode = defaultMode(opts);\n\n await charge(opts.name, actualUsd);\n\n if (mode === \"audit\") return;\n await dispatchCallbacksFor(opts, mode);\n}\n\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: 3-mode × 3-threshold dispatch table inherently branchy; pulling out helpers loses local context.\nasync function dispatchCallbacksFor(opts: BudgetOptions, mode: BudgetMode): Promise<void> {\n for (const lim of opts.limits) {\n const spent = spentIn(opts.name, lim.window);\n if (lim.limitUsd <= 0) {\n if (spent > 0) await dispatchExceed(opts, lim.window, spent, lim.limitUsd, mode);\n continue;\n }\n const ratio = spent / lim.limitUsd;\n if (ratio >= 1) {\n await dispatchExceed(opts, lim.window, spent, lim.limitUsd, mode);\n } else {\n // Iterate descending so the HIGHEST matched threshold fires (0.95 not 0.8)\n for (const t of [...THRESHOLDS].reverse() as Threshold[]) {\n if (ratio >= t) {\n await dispatchThreshold(opts, lim.window, spent, lim.limitUsd, t);\n break;\n }\n }\n }\n }\n}\n\nasync function dispatchThreshold(\n opts: BudgetOptions,\n window: BudgetOptions[\"limits\"][number][\"window\"],\n spentUsd: number,\n limitUsd: number,\n threshold: Threshold,\n): Promise<void> {\n if (opts.onThreshold === undefined) return;\n try {\n await opts.onThreshold({\n budgetName: opts.name,\n window,\n threshold,\n spentUsd,\n limitUsd,\n });\n } catch (err) {\n // EC-8: callback throw isolated\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[budget] onThreshold callback threw: ${msg}\\n`);\n }\n}\n\nasync function dispatchExceed(\n opts: BudgetOptions,\n window: BudgetOptions[\"limits\"][number][\"window\"],\n spentUsd: number,\n limitUsd: number,\n mode: BudgetMode,\n): Promise<void> {\n if (opts.onExceed === undefined) {\n if (mode === \"warn\") {\n process.stderr.write(\n `[budget] \"${opts.name}\" exceeded ${window} limit: $${spentUsd.toFixed(4)} > $${limitUsd.toFixed(4)}\\n`,\n );\n }\n return;\n }\n try {\n await opts.onExceed({\n budgetName: opts.name,\n window,\n spentUsd,\n limitUsd,\n mode,\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[budget] onExceed callback threw: ${msg}\\n`);\n }\n}\n","/**\n * normalizeUsage — convert provider-shaped raw `usage` object to\n * canonical `TokenUsage`. Ports Hermes Agent's `normalize_usage`\n * (referencia/hermes-agent/agent/usage_pricing.py:672-742).\n *\n * Handles 3 API shapes:\n * - Anthropic Messages: 4 explicit buckets (input/output/cache_read/cache_creation).\n * - OpenAI Chat Completions: prompt_tokens INCLUDES cache; subtract cached_tokens.\n * - OpenAI Responses (Codex): input_tokens INCLUDES cache; same subtraction.\n *\n * Edge cases:\n * - cline#10266 — OpenAI-compat proxies (OpenRouter, Vercel AI Gateway,\n * Cline) routing Claude expose Anthropic-style top-level fields\n * (cache_read_input_tokens / cache_creation_input_tokens). Both\n * locations are checked with top-level fallback.\n * - Null/undefined fields → 0 via `int()` coerce.\n * - String token counts → parsed via int.\n * - Negative values → clamped to 0 (defensive against proxy bugs).\n *\n * @internal\n */\n\nimport type { TokenUsage } from \"@theokit/sdk\";\n\ntype ApiMode = \"anthropic_messages\" | \"openai_chat_completions\" | \"openai_responses\";\n\nfunction int(v: unknown): number {\n if (typeof v === \"number\") return Number.isFinite(v) ? Math.max(0, Math.trunc(v)) : 0;\n if (typeof v === \"string\") {\n const n = Number.parseInt(v, 10);\n return Number.isFinite(n) ? Math.max(0, n) : 0;\n }\n return 0;\n}\n\nfunction buildTotal(buckets: {\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n}): number {\n // total = visible input + cache buckets + output (reasoning counted via output)\n return (\n buckets.inputTokens + buckets.outputTokens + buckets.cacheReadTokens + buckets.cacheWriteTokens\n );\n}\n\nfunction omitUndefined(usage: {\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n reasoningTokens: number;\n totalTokens: number;\n}): TokenUsage {\n return {\n inputTokens: usage.inputTokens,\n outputTokens: usage.outputTokens,\n ...(usage.cacheReadTokens > 0 ? { cacheReadTokens: usage.cacheReadTokens } : {}),\n ...(usage.cacheWriteTokens > 0 ? { cacheWriteTokens: usage.cacheWriteTokens } : {}),\n ...(usage.reasoningTokens > 0 ? { reasoningTokens: usage.reasoningTokens } : {}),\n totalTokens: usage.totalTokens,\n };\n}\n\nexport function inferApiMode(provider: string): ApiMode {\n const p = provider.toLowerCase();\n if (p === \"anthropic\" || p === \"claude\" || p === \"bedrock_anthropic\") {\n return \"anthropic_messages\";\n }\n if (p === \"openai-codex\" || p === \"codex\") return \"openai_responses\";\n // openai, openrouter, deepseek, google (compat), ollama (compat), lmstudio (compat), etc\n return \"openai_chat_completions\";\n}\n\ninterface RawRecord {\n [k: string]: unknown;\n}\n\nexport function normalizeUsage(\n rawUsage: unknown,\n opts: { provider: string; apiMode?: ApiMode },\n): TokenUsage {\n if (rawUsage === null || rawUsage === undefined) {\n return { inputTokens: 0, outputTokens: 0, totalTokens: 0 };\n }\n if (typeof rawUsage !== \"object\") {\n return { inputTokens: 0, outputTokens: 0, totalTokens: 0 };\n }\n const raw = rawUsage as RawRecord;\n const mode = opts.apiMode ?? inferApiMode(opts.provider);\n\n if (mode === \"anthropic_messages\") return normalizeAnthropic(raw);\n if (mode === \"openai_responses\") return normalizeOpenAIResponses(raw);\n return normalizeOpenAIChat(raw);\n}\n\nfunction normalizeAnthropic(raw: RawRecord): TokenUsage {\n const inputTokens = int(raw.input_tokens);\n const outputTokens = int(raw.output_tokens);\n const cacheReadTokens = int(raw.cache_read_input_tokens);\n const cacheWriteTokens = int(raw.cache_creation_input_tokens);\n const usage = {\n inputTokens,\n outputTokens,\n cacheReadTokens,\n cacheWriteTokens,\n reasoningTokens: 0,\n totalTokens: buildTotal({ inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens }),\n };\n return omitUndefined(usage);\n}\n\nfunction normalizeOpenAIResponses(raw: RawRecord): TokenUsage {\n const inputTotal = int(raw.input_tokens);\n const outputTokens = int(raw.output_tokens);\n const inputDetails = (raw.input_tokens_details as RawRecord | undefined) ?? {};\n const outputDetails = (raw.output_tokens_details as RawRecord | undefined) ?? {};\n const cacheReadTokens = int(inputDetails.cached_tokens);\n const cacheWriteTokens = int(inputDetails.cache_creation_tokens);\n const reasoningTokens = int(outputDetails.reasoning_tokens);\n const inputTokens = Math.max(0, inputTotal - cacheReadTokens - cacheWriteTokens);\n const usage = {\n inputTokens,\n outputTokens,\n cacheReadTokens,\n cacheWriteTokens,\n reasoningTokens,\n totalTokens: buildTotal({ inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens }),\n };\n return omitUndefined(usage);\n}\n\nfunction normalizeOpenAIChat(raw: RawRecord): TokenUsage {\n const promptTotal = int(raw.prompt_tokens);\n const outputTokens = int(raw.completion_tokens);\n const promptDetails = (raw.prompt_tokens_details as RawRecord | undefined) ?? {};\n const completionDetails = (raw.completion_tokens_details as RawRecord | undefined) ?? {};\n\n // cline#10266 fallback — proxies expose Anthropic-style top-level fields when routing Claude\n const cacheReadTokens = int(promptDetails.cached_tokens) || int(raw.cache_read_input_tokens);\n const cacheWriteTokens =\n int(promptDetails.cache_write_tokens) || int(raw.cache_creation_input_tokens);\n\n const reasoningTokens = int(completionDetails.reasoning_tokens);\n const inputTokens = Math.max(0, promptTotal - cacheReadTokens - cacheWriteTokens);\n const usage = {\n inputTokens,\n outputTokens,\n cacheReadTokens,\n cacheWriteTokens,\n reasoningTokens,\n totalTokens: buildTotal({ inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens }),\n };\n return omitUndefined(usage);\n}\n","/**\n * Built-in per-model USD pricing table (SDK 2.0 Phase 2 / sdk-budget T2.X).\n *\n * Prices are in USD per 1M tokens (input / output). Sources: each\n * provider's public pricing page; values verified 2026-06 against\n * the same tier used in sdk-core's `internal/budget/pricing-data.json`.\n *\n * The table is intentionally CONSERVATIVE — only the most-used models\n * land here. Consumers needing exhaustive coverage can supply an\n * override via `createUsdBudgetTracker({ pricing })`.\n *\n * @public\n */\n\nexport interface ModelPricing {\n /** USD per 1,000,000 input tokens. */\n readonly inputPerMillionUsd: number;\n /** USD per 1,000,000 output tokens. */\n readonly outputPerMillionUsd: number;\n}\n\n/** Built-in pricing table — exhaustive enough for v0.1; users supply overrides for niche models. */\nexport const BUILTIN_PRICING: Readonly<Record<string, ModelPricing>> = Object.freeze({\n // OpenAI / OpenRouter aliases\n \"openai/gpt-4o\": { inputPerMillionUsd: 2.5, outputPerMillionUsd: 10 },\n \"openai/gpt-4o-mini\": { inputPerMillionUsd: 0.15, outputPerMillionUsd: 0.6 },\n \"openai/gpt-4-turbo\": { inputPerMillionUsd: 10, outputPerMillionUsd: 30 },\n \"openai/gpt-3.5-turbo\": { inputPerMillionUsd: 0.5, outputPerMillionUsd: 1.5 },\n // Anthropic / Claude\n \"anthropic/claude-3-5-sonnet-latest\": { inputPerMillionUsd: 3, outputPerMillionUsd: 15 },\n \"anthropic/claude-3-5-haiku-latest\": { inputPerMillionUsd: 0.8, outputPerMillionUsd: 4 },\n \"anthropic/claude-3-opus-latest\": { inputPerMillionUsd: 15, outputPerMillionUsd: 75 },\n // Google\n \"google/gemini-1.5-pro\": { inputPerMillionUsd: 1.25, outputPerMillionUsd: 5 },\n \"google/gemini-1.5-flash\": { inputPerMillionUsd: 0.075, outputPerMillionUsd: 0.3 },\n});\n\n/**\n * Compute USD cost for a single token event. Returns 0 (NOT throws)\n * when the model is unknown — consumers should override the pricing\n * table to add their model.\n */\nexport function computeUsdCost(\n pricing: Readonly<Record<string, ModelPricing>>,\n model: string,\n type: \"input\" | \"output\",\n tokens: number,\n): number {\n const entry = pricing[model];\n if (entry === undefined) return 0;\n const ratePerMillion = type === \"input\" ? entry.inputPerMillionUsd : entry.outputPerMillionUsd;\n if (!Number.isFinite(tokens) || tokens <= 0) return 0;\n return (tokens / 1_000_000) * ratePerMillion;\n}\n","/**\n * `createUsdBudgetTracker` — USD-cost-aware `BudgetTracker` impl shipped\n * in `@theokit/sdk-budget` (SDK 2.0 Phase 2 / T2.X).\n *\n * Extends the counter pattern from `createCounterBudgetTracker`\n * (sdk-core reference impl) with per-model USD cost computation via\n * the `BUILTIN_PRICING` table.\n *\n * Use cases:\n * - Cap total spend per agent run (`maxUsd`).\n * - Cap total tokens (`maxTokens`) AND USD ceiling simultaneously.\n * - Observe cumulative USD from outside (`getTotalUsd()`).\n *\n * Tracker.check() returns `allowed: false` when ANY of the configured\n * caps is exceeded. `reason` is one of:\n * - `\"token_limit\"` — `maxTokens` reached\n * - `\"cost_limit\"` — `maxUsd` reached\n *\n * Layered design (mirrors counter impl):\n * - `track()` is sync + non-throwing (clamps invalid values).\n * - `check()` is sync (allowed: true unless a cap is exceeded).\n * - `getTotal()` returns the SDK-required token + iteration totals\n * ONLY (USD is exposed via the bonus `getTotalUsd()` method).\n *\n * @public\n */\n\nimport type { BudgetCheck, BudgetTotal, BudgetTracker, BudgetUsageEvent } from \"@theokit/sdk\";\nimport { BUILTIN_PRICING, computeUsdCost, type ModelPricing } from \"./usd-pricing.js\";\n\n/** Options for `createUsdBudgetTracker`. */\nexport interface UsdBudgetTrackerOptions {\n /** Hard ceiling on total tokens (input + output combined). */\n readonly maxTokens?: number;\n /** Hard ceiling on cumulative USD spend. */\n readonly maxUsd?: number;\n /** Optional override map — model id → pricing entry. Merged onto BUILTIN_PRICING. */\n readonly pricing?: Readonly<Record<string, ModelPricing>>;\n}\n\n/**\n * Build a fresh USD-aware tracker. The returned object exposes the\n * `BudgetTracker` contract PLUS `getTotalUsd()` + `nextIteration()`\n * helpers for explicit iteration counting.\n */\nexport function createUsdBudgetTracker(\n options: UsdBudgetTrackerOptions = {},\n): BudgetTracker & { nextIteration(): void; getTotalUsd(): number } {\n let totalTokens = 0;\n let iterations = 0;\n let totalUsd = 0;\n const maxTokens = options.maxTokens;\n const maxUsd = options.maxUsd;\n const pricing: Readonly<Record<string, ModelPricing>> =\n options.pricing !== undefined ? { ...BUILTIN_PRICING, ...options.pricing } : BUILTIN_PRICING;\n\n return {\n track(event: BudgetUsageEvent): void {\n // Sync + non-throwing per contract. Invalid events silently clamped.\n const t = Number.isFinite(event.tokens) && event.tokens > 0 ? event.tokens : 0;\n if (t === 0) return;\n totalTokens += t;\n totalUsd += computeUsdCost(pricing, event.model, event.type, t);\n },\n\n check(): BudgetCheck {\n if (maxUsd !== undefined && totalUsd >= maxUsd) {\n return {\n allowed: false,\n reason: \"cost_limit\",\n detail: `USD ${totalUsd.toFixed(6)} >= maxUsd ${maxUsd}`,\n };\n }\n if (maxTokens !== undefined && totalTokens >= maxTokens) {\n return {\n allowed: false,\n reason: \"token_limit\",\n detail: `${totalTokens} >= maxTokens ${maxTokens}`,\n };\n }\n return { allowed: true };\n },\n\n getTotal(): BudgetTotal {\n return { tokens: totalTokens, iterations };\n },\n\n getTotalUsd(): number {\n return totalUsd;\n },\n\n nextIteration(): void {\n iterations += 1;\n },\n };\n}\n"]}