@theokit/sdk-budget 0.3.1 → 0.3.3
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/CHANGELOG.md +107 -0
- package/LICENSE +2 -2
- package/README.md +22 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +284 -32
- package/dist/index.d.ts +284 -32
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,112 @@
|
|
|
1
1
|
# Changelog — @theokit/sdk-budget
|
|
2
2
|
|
|
3
|
+
## 0.3.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e3f2a82: Public-API documentation reviewed file by file, and corrected wherever it disagreed
|
|
8
|
+
with the code. The docblocks ship in the `.d.ts`, so these read as behaviour changes
|
|
9
|
+
in an editor even though no behaviour changed.
|
|
10
|
+
|
|
11
|
+
The corrections that change what a caller would do:
|
|
12
|
+
|
|
13
|
+
- **`sdk-cache` documented its own premise backwards.** The header example labelled a
|
|
14
|
+
semantic hit as if it avoided the provider call. `asPlugin()` returns the cached
|
|
15
|
+
answer as `recalledContext`, which the agent loop injects as a `<memory-context>`
|
|
16
|
+
block _before_ the prompt — the request still goes to the provider. The two modes
|
|
17
|
+
are now labelled separately, with a table saying which one short-circuits and which
|
|
18
|
+
one seeds.
|
|
19
|
+
- **`sdk-handoff`'s five error classes said "throw".** Under the plugin wiring the
|
|
20
|
+
handler never throws; every failure becomes a tool result `{"ok":false,…}` handed
|
|
21
|
+
back to the model. Each class now says where it is actually observable. The header
|
|
22
|
+
also told readers to `import { Handoff } from "@theokit/sdk"`, from which it was
|
|
23
|
+
extracted.
|
|
24
|
+
- **`sdk-budget`'s `charge()` claimed idempotency across concurrent calls.** The mutex
|
|
25
|
+
serialises, it does not deduplicate: two identical calls record twice. Related, and
|
|
26
|
+
newly documented: with `maxUsd` set, a model missing from the pricing table denies
|
|
27
|
+
every request rather than passing it — and the table matches by exact string, so
|
|
28
|
+
`"openai/gpt-4o"` does not match `"gpt-4o"`.
|
|
29
|
+
- **The three `memory-*` adapters advertised an env-var fallback they do not read**,
|
|
30
|
+
and their peer dependencies are required rather than optional. Their behavioural
|
|
31
|
+
differences are now stated where they break the "interchangeable adapter"
|
|
32
|
+
assumption — honcho ignores `k` and always throws on `delete`; mem0 recalls across
|
|
33
|
+
sessions by design; supermemory ignores `sessionId` entirely.
|
|
34
|
+
- **`sdk-memory`'s `truncated` flag was documented as its own inverse**, and its
|
|
35
|
+
dreaming sweep claimed a mutex it never takes against the writer it names.
|
|
36
|
+
- **`sdk-tools`** corrected `run_vitest`'s unreachable `no_vitest` code, `truncation`'s
|
|
37
|
+
replacement-character claim, and two return shapes missing a live error code.
|
|
38
|
+
- **`acp`/`cli`** corrected sixteen statements including a named error class that is
|
|
39
|
+
not the one raised, a handler documented as calling `fork()` that refuses
|
|
40
|
+
unconditionally, handlers described as pure that mint ids and mutate a store, a
|
|
41
|
+
config loader credited to Zod in a package that does not import it, and a `--force`
|
|
42
|
+
scaffold described as atomic that deletes the destination before the rename.
|
|
43
|
+
|
|
44
|
+
Undocumented public symbols were documented across every package, with each claim
|
|
45
|
+
checked against the implementation rather than inferred from the name.
|
|
46
|
+
|
|
47
|
+
- e368fc1: Every published declaration file now compiles without `skipLibCheck` (#345). The
|
|
48
|
+
DTS rollup emitted symbols as a re-export from a chunk while omitting them from
|
|
49
|
+
that chunk's `import`, and dropped type-only imports from external packages —
|
|
50
|
+
leaving 51 unresolved references across ten of the twelve packages. Nothing broke
|
|
51
|
+
at runtime, and `tsc` stayed green for anyone with `skipLibCheck` on, but a
|
|
52
|
+
consumer running type-aware lint saw every type reached through one degrade to
|
|
53
|
+
`error`.
|
|
54
|
+
|
|
55
|
+
The declarations are repaired at build time from the compiler's own diagnostics.
|
|
56
|
+
No source or API change.
|
|
57
|
+
|
|
58
|
+
- e699569: **The repository moved to the official `usetheokit` organization.** Every `repository`, `bugs` and `homepage` field now points there, along with the README, `CONTRIBUTING.md`, `SECURITY.md` and the issue templates. Existing clones and any URL already published keep working — GitHub redirects a transferred repository permanently — so this is a correctness fix for the metadata npm renders, not a break.
|
|
59
|
+
|
|
60
|
+
**The Apache-2.0 text every package ships was replaced with the official one.** The copy distributed until now had paragraph 4(d) truncated: it read "except as required for describing the origin of the Work and reproducing the content of the NOTICE file", dropping "reasonable and customary use" from the licensed clause. §4(d) governs what a redistributor must do with attribution notices, and the omission narrowed it.
|
|
61
|
+
|
|
62
|
+
That matters more than a typo would. The manifests declare the SPDX identifier `Apache-2.0`, which is an assertion that the terms are _the_ Apache-2.0 terms — a licence scanner resolves the identifier and never reads the file. A consumer's compliance review, which does read the file, would find a body that no longer matches the identifier and has no name of its own. Every `LICENSE` in this repository is now byte-identical to the canonical text, with the appendix filled in.
|
|
63
|
+
|
|
64
|
+
Nothing else about the terms changed: the licence is the same licence it has always been meant to be, and no package changes what it grants.
|
|
65
|
+
|
|
66
|
+
- c7385d2: Test runs no longer claim every core on the host.
|
|
67
|
+
|
|
68
|
+
None of the package configs capped `maxWorkers`, so vitest's default applied: `os.availableParallelism()`,
|
|
69
|
+
one fork per core, each booting a full test environment. The repo's `test` script is
|
|
70
|
+
`turbo run test --filter='./packages/*'`, so that default is paid once per package _concurrently_ —
|
|
71
|
+
nproc forks times turbo's concurrency, on nproc cores. Measured on a 12-thread machine during an
|
|
72
|
+
unrelated investigation, two vitest pools alone were enough to reach load average 33.89 with the
|
|
73
|
+
desktop unusable; a full fan-out is several times that.
|
|
74
|
+
|
|
75
|
+
`@theokit/sdk` is the interesting case. B-104 recorded on 2026-08-19 that the `poolOptions.forks.*`
|
|
76
|
+
block was 100% dead in Vitest 4, deleted it, and noted that `fileParallelism: false` was forcing
|
|
77
|
+
`maxWorkers` to 1 unconditionally, so a fork-count knob could not act. B-059 then flipped
|
|
78
|
+
`fileParallelism` to `true` on 2026-08-20, which made the knob able to act again — and nothing
|
|
79
|
+
reintroduced one, so the package silently went back to the uncapped default. That comment has been
|
|
80
|
+
corrected along with the config; it claimed no knob existed, which is no longer true.
|
|
81
|
+
|
|
82
|
+
The cap leaves 4 cores free (`Math.max(2, cpus().length - 4)`), scaling with the runner rather than
|
|
83
|
+
hard-coding one machine's core count. It costs no wall-clock: measured in `theokit-ui`, the full
|
|
84
|
+
suite ran 73.96s at 4 workers against 74.36s at 12, so the parallelism above the cap was already
|
|
85
|
+
noise. Verified as resolved config rather than as file contents — `createVitest` reports
|
|
86
|
+
`maxWorkers: 8` on a 12-thread host, which is the formula, not the default.
|
|
87
|
+
|
|
88
|
+
This changes no published behaviour; it is test tooling only. Refs usetheokit/theokit-ui#51.
|
|
89
|
+
|
|
90
|
+
## 0.3.2
|
|
91
|
+
|
|
92
|
+
### Patch Changes
|
|
93
|
+
|
|
94
|
+
- 8790f70: Refuse a `workspace:` range before it can reach npm.
|
|
95
|
+
|
|
96
|
+
Five of this repo's twelve publishable packages declare internal dependencies as `workspace:^`, which
|
|
97
|
+
is correct on disk and becomes an unrecoverable defect if the publish goes out through a tool that
|
|
98
|
+
does not rewrite it: `pnpm` resolves the protocol while packing, `npm` ships the manifest verbatim.
|
|
99
|
+
A version published that way fails to install for everyone and cannot be corrected — only
|
|
100
|
+
deprecated.
|
|
101
|
+
|
|
102
|
+
Every publishable package now runs the guard in `prepublishOnly`, so it fires whichever way the
|
|
103
|
+
publish is invoked, and `pnpm release` runs it once across the repo before `changeset publish`.
|
|
104
|
+
|
|
105
|
+
Note for anyone reading a published manifest: the `prepublishOnly` entry points at a path inside
|
|
106
|
+
this repository. It never runs for a consumer — the hook only fires when the package itself is
|
|
107
|
+
published — and guarding the entry point that a hand-run `npm publish` actually uses was worth the
|
|
108
|
+
cosmetic wart of shipping the line.
|
|
109
|
+
|
|
3
110
|
## 0.3.1
|
|
4
111
|
|
|
5
112
|
### Patch Changes
|
package/LICENSE
CHANGED
|
@@ -137,8 +137,8 @@
|
|
|
137
137
|
|
|
138
138
|
6. Trademarks. This License does not grant permission to use the trade
|
|
139
139
|
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
-
except as required for
|
|
141
|
-
reproducing the content of the NOTICE file.
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
142
|
|
|
143
143
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
144
|
agreed to in writing, Licensor provides the Work (and each
|
package/README.md
CHANGED
|
@@ -4,6 +4,15 @@ USD-cost-aware `BudgetTracker` impls for [`@theokit/sdk`](https://www.npmjs.com/
|
|
|
4
4
|
Consumes the kernel-facing `BudgetTracker` port (SDK 2.0 Phase 2 / T2.1 —
|
|
5
5
|
ADR D1, interface inversion).
|
|
6
6
|
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @theokit/sdk-budget
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`@theokit/sdk` (>=4.0.0) is a peer dependency — install it alongside. Nothing else is required:
|
|
14
|
+
this package has no runtime dependencies of its own.
|
|
15
|
+
|
|
7
16
|
```ts
|
|
8
17
|
import { Agent } from "@theokit/sdk";
|
|
9
18
|
import { createUsdBudgetTracker } from "@theokit/sdk-budget";
|
|
@@ -104,6 +113,19 @@ subsystem uses. No double-mutex hazard.
|
|
|
104
113
|
- v0.3: Ledger persistence (Postgres / SQLite adapters).
|
|
105
114
|
- v0.4: Per-user / per-tenant budget aggregation.
|
|
106
115
|
|
|
116
|
+
## API reference
|
|
117
|
+
|
|
118
|
+
Every symbol this package exports, with the exact specifier to import it from, is in the generated
|
|
119
|
+
capability map that ships inside `@theokit/sdk`:
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
node_modules/@theokit/sdk/docs/harness-capability-map.md # symbol -> import specifier
|
|
123
|
+
node_modules/@theokit/sdk/docs/error-codes.md # every `code` an error can carry
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Both are generated from the built type declarations, so they describe the version you installed
|
|
127
|
+
rather than the version someone wrote a page about.
|
|
128
|
+
|
|
107
129
|
## License
|
|
108
130
|
|
|
109
131
|
Apache-2.0 © useTheo
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/format-cost.ts","../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 * M7-6 — honest-null cost render helper. Turns the `number | undefined` cost\n * (from `computeUsdCost` / `getTotalUsd`, M1-6) into a display string: an\n * unknown cost renders as `\"—\"` (NEVER a dishonest `\"$0\"`), a real number\n * renders as `\"$X.XX\"`. Composes the cost-honesty contract — a known-zero cost\n * (`0`) is distinct from an unknown one (`undefined`).\n *\n * @public\n */\n\n/** Options for {@link formatCostUsd}. */\nexport interface FormatCostUsdOptions {\n /** Marker for an unknown cost (`undefined`). Default `\"—\"`. */\n readonly unknown?: string;\n /** Currency prefix for a known cost. Default `\"$\"`. */\n readonly currency?: string;\n}\n\n/**\n * Render a USD cost for display. `undefined` -> the unknown marker (`\"—\"` by\n * default); a number -> `\"$X.XX\"` (2 decimal places). `0` is a real known-zero\n * and renders `\"$0.00\"`, NOT the unknown marker.\n */\nexport function formatCostUsd(cost: number | undefined, opts: FormatCostUsdOptions = {}): string {\n if (cost === undefined) return opts.unknown ?? \"—\";\n return `${opts.currency ?? \"$\"}${cost.toFixed(2)}`;\n}\n","/**\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.\n *\n * Returns `undefined` (NOT `0`, NOT throws) when the model is UNKNOWN — its\n * cost is genuinely unknown, and coercing it to `$0` would be dishonest (cost\n * contract `D377-cost-status-closed-enum.md`: amount-unknown ≠ `$0`). Consumers\n * who want a price for a niche model supply an override via\n * `createUsdBudgetTracker({ pricing })`. A KNOWN model with zero/invalid tokens\n * returns `0` — that is a real, known `$0`.\n */\nexport function computeUsdCost(\n pricing: Readonly<Record<string, ModelPricing>>,\n model: string,\n type: \"input\" | \"output\",\n tokens: number,\n): number | undefined {\n const entry = pricing[model];\n if (entry === undefined) return undefined;\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/** The cost-cap denial for the current state, or `null` if the cost cap is satisfied. */\nfunction evaluateCostCap(\n maxUsd: number | undefined,\n costKnown: boolean,\n totalUsd: number,\n): BudgetCheck | null {\n if (maxUsd === undefined) return null;\n if (!costKnown) {\n // Fail closed: a spend cap is set but cost is unknown — we cannot prove the\n // run is under budget, so deny rather than allow unbounded spend.\n return {\n allowed: false,\n reason: \"cost_limit\",\n detail: `cost unknown — cannot verify maxUsd ${maxUsd}`,\n };\n }\n if (totalUsd >= maxUsd) {\n return {\n allowed: false,\n reason: \"cost_limit\",\n detail: `USD ${totalUsd.toFixed(6)} >= maxUsd ${maxUsd}`,\n };\n }\n return null;\n}\n\n/** The token-cap denial for the current state, or `null` if the token cap is satisfied. */\nfunction evaluateTokenCap(maxTokens: number | undefined, totalTokens: number): BudgetCheck | null {\n if (maxTokens !== undefined && totalTokens >= maxTokens) {\n return {\n allowed: false,\n reason: \"token_limit\",\n detail: `${totalTokens} >= maxTokens ${maxTokens}`,\n };\n }\n return null;\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 | undefined } {\n let totalTokens = 0;\n let iterations = 0;\n let totalUsd = 0;\n // Honest-null (D377): once any round's cost is UNKNOWN, the aggregate becomes\n // unknown and STAYS unknown — a later known round does not resurrect it.\n let costKnown = true;\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 const cost = computeUsdCost(pricing, event.model, event.type, t);\n if (cost === undefined) {\n costKnown = false; // poison — do NOT add 0 (that would be a dishonest $0)\n return;\n }\n if (costKnown) totalUsd += cost;\n },\n\n check(): BudgetCheck {\n // Cost cap is evaluated first (USD is the higher-signal denial for a human).\n return (\n evaluateCostCap(maxUsd, costKnown, totalUsd) ??\n evaluateTokenCap(maxTokens, totalTokens) ?? { allowed: true }\n );\n },\n\n getTotal(): BudgetTotal {\n return { tokens: totalTokens, iterations };\n },\n\n getTotalUsd(): number | undefined {\n return costKnown ? totalUsd : undefined;\n },\n\n nextIteration(): void {\n iterations += 1;\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/format-cost.ts","../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 * M7-6 — honest-null cost render helper. Turns the `number | undefined` cost\n * (from `computeUsdCost` / `getTotalUsd`, M1-6) into a display string: an\n * unknown cost renders as `\"—\"` (NEVER a dishonest `\"$0\"`), a real number\n * renders as `\"$X.XX\"`. Composes the cost-honesty contract — a known-zero cost\n * (`0`) is distinct from an unknown one (`undefined`).\n *\n * @public\n */\n\n/** Options for {@link formatCostUsd}. */\nexport interface FormatCostUsdOptions {\n /** Marker for an unknown cost (`undefined`). Default `\"—\"`. */\n readonly unknown?: string;\n /** Currency prefix for a known cost. Default `\"$\"`. */\n readonly currency?: string;\n}\n\n/**\n * Render a USD cost for display. `undefined` -> the unknown marker (`\"—\"` by\n * default); a number -> `\"$X.XX\"` (2 decimal places). `0` is a real known-zero\n * and renders `\"$0.00\"`, NOT the unknown marker.\n */\nexport function formatCostUsd(cost: number | undefined, opts: FormatCostUsdOptions = {}): string {\n if (cost === undefined) return opts.unknown ?? \"—\";\n return `${opts.currency ?? \"$\"}${cost.toFixed(2)}`;\n}\n","/**\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\n/**\n * Midnight UTC of `now`'s day — the start of a `1d` budget window.\n *\n * Always UTC, never the host's local timezone: \"1 USD per day\" resets at 00:00Z, so a budget in\n * UTC-05:00 resets at 19:00 local. That is deliberate — a budget shared by processes in different\n * regions must reset at one instant — but it surprises anyone reading a daily total at local\n * midnight.\n */\nexport function startOfDayUtc(now: Date = new Date()): Date {\n return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));\n}\n\n/**\n * Midnight UTC on the MONDAY of `now`'s week — the start of a `1w` budget window.\n *\n * ISO 8601, so the week starts on Monday, not Sunday. This is calendar-aligned rather than rolling:\n * \"5 USD per week\" resets on Monday, which is what a person reading a weekly budget expects, and not\n * a trailing 168 hours. `30d` and `365d` are relative for the mirror-image reason — nobody expects a\n * monthly budget to reset on the 1st.\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/**\n * Inclusive start timestamp (ms) of `window`, as `spentIn` uses it to decide which charges count.\n *\n * Two different behaviours behind one enum, and the difference is visible in every total:\n *\n * - `1d` / `1w` are CALENDAR-aligned to UTC (midnight; ISO Monday). Spend resets at a boundary.\n * - `1h` / `30d` / `365d` are ROLLING — `now` minus the duration. Nothing ever \"resets\"; the\n * oldest charges simply fall out of the window.\n *\n * Throws on a value outside `BudgetWindow`, which TypeScript already prevents.\n */\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 INSIDE the same mutex as charge — no race.\n * EC-9: charge() is called inside the 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\nconst 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/**\n * Append `amountUsd` to the spend ledger for the budget called `name`, timestamped now.\n *\n * NOT idempotent. `withCwdMutex` SERIALIZES concurrent calls so two appends cannot interleave; it\n * does not deduplicate them. Calling this twice with the same arguments records the spend twice,\n * and there is no charge id to reconcile against — an at-least-once caller needs its own guard.\n *\n * NOT VALIDATED against the registry either: charging a name that was never passed to\n * `createBudget` (a typo, say) succeeds silently and accumulates spend that no limit enforces and\n * that `snapshotAll` — which iterates the REGISTRY — will never show.\n *\n * `chargeAndCheckThresholds` does not rescue you from that, and swapping to it trades one silent\n * failure for another: on an unknown or already-deleted name it writes one line to stderr and\n * RETURNS WITHOUT CHARGING, so the spend is lost rather than merely invisible. Neither function\n * throws. Validate the name against `getBudgetOptionsRaw` / `getBudget` if it matters which of the\n * two failures you get.\n *\n * `amountUsd <= 0` is a no-op, so a refund cannot be expressed here.\n *\n * The ledger is a MODULE-LEVEL SINGLETON: process-wide, shared by every budget and every agent in\n * the process, and lost on exit — there is no persistence across restarts. Entries older than a\n * year are garbage-collected opportunistically.\n */\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/**\n * Total USD recorded for `name` inside `window`, summed at call time.\n *\n * Returns `0` for a name that was never charged — indistinguishable from a budget that exists and\n * has spent nothing. Use `getBudget(name)` when you need to tell those apart.\n *\n * Lock-free: it reads the live array without taking the mutex, so a charge landing mid-read is\n * simply included or not. Window boundaries are those of {@link windowStartMs} — `1d` and `1w` are\n * UTC calendar-aligned, the rest are rolling.\n */\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 * 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 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\n/**\n * Register a budget under `opts.name` and return its live handle.\n *\n * ```ts\n * const b = createBudget({ name: \"daily\", mode: \"block\", limits: [{ window: \"1d\", limitUsd: 5 }] });\n * ```\n *\n * THROWS `ConfigurationError(code: \"invalid_budget_name\")` in two cases: a name that does not match\n * `^[a-z0-9][a-z0-9_-]*$` (lowercase only — `\"Daily\"` and `\"my.budget\"` are rejected), and a name\n * already registered. Duplicate registration is deliberately an error rather than an idempotent\n * return, so a second `createBudget(\"daily\", …)` with different limits cannot silently win.\n *\n * Registering does NOT reset spend. The ledger is keyed by NAME and outlives the registry entry, so\n * re-creating a budget after `deleteBudget` inherits everything charged under that name — a\n * config reload keeps enforcing the day's spend, which is usually right and is a surprise if you\n * expected a fresh start.\n *\n * Process-local and non-persistent: nothing survives a restart, so re-create budgets at startup.\n * `mode` defaults to `\"warn\"` (see {@link defaultMode}) — a budget created without one observes and\n * does not block.\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\n/**\n * The live handle for a registered budget, or `undefined` when the name is unknown.\n *\n * `undefined` is the honest answer for \"never created\" — it does NOT mean \"zero spend\". A budget\n * that exists but has not been charged returns a handle whose `spentIn(...)` is 0, and the two are\n * different facts: the first means nothing is enforcing a limit.\n *\n * The registry is per-PROCESS and holds no persistence, so a fresh process starts with no budgets\n * and no ledger. Re-create them at startup.\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\n/**\n * Every budget registered in this process, in insertion order.\n *\n * Empty after a restart — see {@link getBudget} on process-local state. Use it to enumerate what is\n * being enforced; use {@link snapshotAll} when you want the numbers rather than the handles.\n */\nexport function listBudgets(): readonly BudgetHandle[] {\n return [...registry.values()].map(buildHandle);\n}\n\n/**\n * Remove a budget from the registry. Returns `false` when the name was not registered.\n *\n * This stops ENFORCEMENT; it does not refund or clear the ledger. Re-creating a budget under the\n * same name inherits the spend already recorded for that name, which is usually what you want after\n * a config reload and a surprise if you were expecting a reset.\n */\nexport function deleteBudget(name: string): boolean {\n return registry.delete(name);\n}\n\n/**\n * One row per budget PER WINDOW — a budget with three limits produces three rows, not one.\n *\n * `ratio` is `spentUsd / limitUsd`, and is 0 when the limit is 0 rather than `Infinity`, so a\n * zero-limit budget does not poison a dashboard that sums or charts these. Spend is computed at call\n * time from the ledger, counting only entries inside each window, so the same budget reports\n * different numbers for `1d` and `30d`.\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\n/**\n * The stored {@link BudgetOptions} exactly as registered, or `undefined` when the name is unknown.\n *\n * Distinct from {@link getBudget}, which returns a HANDLE with live accessors. Use this when you\n * need the configuration itself — the limits array, the callbacks, the declared mode — for example\n * to render it or to re-register the same shape elsewhere.\n *\n * The object is the registry's own, held by reference: mutating it changes what the budget enforces\n * without going through `Budget.create`, which is a bug waiting to happen. Copy before editing.\n */\nexport function getBudgetOptionsRaw(name: string): BudgetOptions | undefined {\n return registry.get(name);\n}\n\n/**\n * The mode a budget enforces, resolving the omitted case to `\"warn\"`.\n *\n * `\"warn\"` fires the callbacks and lets the call through; `\"block\"` refuses it. The default is\n * deliberate: a budget added for observability must not start rejecting traffic because someone\n * forgot a field.\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` before the LLM call if any limit would be\n * exceeded (EC-9 — the caller invokes it inside the mutex section).\n * - `chargeAndCheckThresholds(name, actualUsd)` — apply charge ao\n * ledger + invokes onThreshold/onExceed callbacks isolated in\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 * Refuse an upcoming call that would push a `\"block\"`-mode budget past one of its limits. Call it\n * BEFORE the LLM request, with your cost estimate for that request.\n *\n * Throws `BudgetExceededError` (carrying `budgetName`, `window`, the projected `spentUsd`, the\n * `limitUsd` and `mode: \"block\"`) for the FIRST limit where `alreadySpent + estimatedUsd` exceeds\n * the limit. The comparison is strict, so landing exactly on the limit is allowed through — while\n * the post-charge `onExceed` callback fires at `>=`. The two boundaries do not agree.\n *\n * SILENTLY DOES NOTHING in three cases, and none of them is distinguishable from \"you are within\n * budget\" at the call site:\n *\n * - `name` is not registered — a typo, or a budget deleted since. Nothing is enforced.\n * - the budget's mode is `\"warn\"` or `\"audit\"` — those never block by design.\n * - the estimate is low. Enforcement is only as good as `estimatedUsd`; passing 0 disables it.\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 * Record the ACTUAL cost of a completed call against a budget and fire its callbacks. Call it after\n * the LLM request, paired with `preflightCheck` before it.\n *\n * NEVER THROWS on budget state — including when the charge tips a limit. Enforcement happens on the\n * next `preflightCheck`; this one only records and notifies. A callback that throws is caught and\n * logged to stderr, so your `onThreshold` / `onExceed` cannot break the run either.\n *\n * Per mode:\n *\n * - `\"audit\"` — charge only, no callbacks.\n * - `\"warn\"` — charge, then `onThreshold` at 80% or 95%, then `onExceed` at 100%. With no\n * `onExceed` handler it writes a line to stderr instead.\n *\n * Per limit, exactly ONE callback fires per call: the highest matched threshold, or `onExceed`,\n * never both. But there is NO de-duplication across calls — a budget sitting at 90% fires\n * `onThreshold(0.8)` again on every single charge, and one past its limit fires `onExceed` on\n * every charge forever. Debounce inside your handler if it pages anyone. A budget with three\n * limits evaluates all three, so one call can fire three callbacks.\n * - `\"block\"` — same callbacks as `\"warn\"`, still no throw. Concurrent sends each pass their own\n * preflight, so the last to charge can legitimately tip a limit that preflight had cleared.\n *\n * A DELETED OR UNKNOWN BUDGET LOSES THE CHARGE. The function writes a warning to stderr and\n * returns — the spend is not recorded anywhere, so a budget removed mid-flight under-reports rather\n * than over-reports. This is the one path where the ledger and reality diverge on purpose.\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 * (reference/peer-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 * - a peer#10266 — OpenAI-compat proxies (OpenRouter, a peer vendor AI Gateway,\n * a peer) 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\n/**\n * Which usage dialect a provider reports in, inferred from its name.\n *\n * Providers do not agree on the shape of a usage object: Anthropic reports `input_tokens` /\n * `output_tokens`, OpenAI's Responses API reports yet another, and the large\n * OpenAI-chat-compatible family (openai, openrouter, deepseek, google, ollama, lmstudio, …) shares\n * one. This maps a provider name onto that choice.\n *\n * UNKNOWN NAMES FALL BACK to the OpenAI chat-completions shape, because that is what almost every\n * compatible endpoint speaks — a new proxy usually works without a change here. Pass\n * {@link normalizeUsage}'s `apiMode` explicitly when a provider is compatible in its wire format but\n * not in its name.\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\n/**\n * Turn a provider's raw usage object into the SDK's canonical {@link TokenUsage}.\n *\n * NEVER THROWS, and that is the point: usage arrives from a third party at the end of a successful\n * call, so a missing or malformed field must not fail a request the model already answered. `null`,\n * `undefined` and non-objects all yield an all-zero usage, and unrecognised fields are dropped.\n *\n * The cost of that tolerance is that a zero is ambiguous — it means \"the provider reported nothing\n * usable\" as readily as \"no tokens\". A budget that suddenly stops accruing is the symptom of a\n * dialect mismatch, not of free traffic.\n *\n * `apiMode` overrides the guess {@link inferApiMode} makes from the provider name; supply it for a\n * compatible endpoint whose name is not recognised.\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 // a peer#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\n/**\n * The two rates needed to price one model, in USD per 1,000,000 tokens.\n *\n * List prices only — this package models no cached-input discount, no batch tier and no negotiated\n * rate, so a cost computed here is an upper bound for anyone on a discount and simply wrong for a\n * provider that bills per request.\n *\n * @public\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/**\n * The nine models this package prices out of the box, keyed by the exact `model` string a\n * `BudgetUsageEvent` carries: four OpenAI, three Anthropic, two Google.\n *\n * Lookup is exact-match. No prefix stripping, no aliasing, no fuzzy fallback — `\"gpt-4o\"` and\n * `\"openrouter/openai/gpt-4o\"` both MISS the `\"openai/gpt-4o\"` entry, and a miss means unknown\n * cost, which under a `maxUsd` cap denies the run. Pass overrides through\n * `createUsdBudgetTracker({ pricing })` rather than expecting a match.\n *\n * Prices are a dated snapshot (verified 2026-06) of public list prices and DRIFT — treat a total\n * derived from them as an estimate, not as a bill. `Object.freeze` here is shallow: the map cannot\n * gain keys, but the `ModelPricing` objects inside it are mutable.\n *\n * @public\n */\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.\n *\n * Returns `undefined` (NOT `0`, NOT throws) when the model is UNKNOWN — its\n * cost is genuinely unknown, and coercing it to `$0` would be dishonest (cost\n * contract `D377-cost-status-closed-enum.md`: amount-unknown ≠ `$0`). Consumers\n * who want a price for a niche model supply an override via\n * `createUsdBudgetTracker({ pricing })`. A KNOWN model with zero/invalid tokens\n * returns `0` — that is a real, known `$0`.\n */\nexport function computeUsdCost(\n pricing: Readonly<Record<string, ModelPricing>>,\n model: string,\n type: \"input\" | \"output\",\n tokens: number,\n): number | undefined {\n const entry = pricing[model];\n if (entry === undefined) return undefined;\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/**\n * Options for {@link createUsdBudgetTracker}. Omitting all of them builds a tracker that counts and\n * never denies.\n *\n * @public\n */\nexport interface UsdBudgetTrackerOptions {\n /**\n * Ceiling on total tokens, input and output combined. `check()` denies with\n * `reason: \"token_limit\"` once the running total REACHES it (`>=`, not `>`).\n */\n readonly maxTokens?: number;\n /**\n * Ceiling on cumulative USD. `check()` denies with `reason: \"cost_limit\"` once the total reaches\n * it.\n *\n * TRAP — setting this makes an UNPRICED MODEL DENY EVERYTHING. If any tracked event names a model\n * absent from the pricing table, the total cost becomes permanently unknown, and a cap that\n * cannot be verified fails CLOSED: every subsequent `check()` returns\n * `{ allowed: false, reason: \"cost_limit\", detail: \"cost unknown — …\" }`. This is intentional\n * (better than spending unbounded), but it means a model id that merely differs in spelling from\n * a {@link BUILTIN_PRICING} key halts the agent. Supply `pricing` for anything not in that table,\n * or leave `maxUsd` unset and gate on `maxTokens`.\n */\n readonly maxUsd?: number;\n /**\n * Per-model prices, spread OVER {@link BUILTIN_PRICING} — so an entry here replaces a built-in of\n * the same key, and built-ins you do not name are kept.\n *\n * Keys are matched EXACTLY against `BudgetUsageEvent.model`. There is no prefix, alias or\n * provider-stripping logic: `\"gpt-4o\"` does not match the built-in `\"openai/gpt-4o\"`. Use the\n * same id string you pass to `Agent.create({ model })`.\n */\n readonly pricing?: Readonly<Record<string, ModelPricing>>;\n}\n\n/** The cost-cap denial for the current state, or `null` if the cost cap is satisfied. */\nfunction evaluateCostCap(\n maxUsd: number | undefined,\n costKnown: boolean,\n totalUsd: number,\n): BudgetCheck | null {\n if (maxUsd === undefined) return null;\n if (!costKnown) {\n // Fail closed: a spend cap is set but cost is unknown — we cannot prove the\n // run is under budget, so deny rather than allow unbounded spend.\n return {\n allowed: false,\n reason: \"cost_limit\",\n detail: `cost unknown — cannot verify maxUsd ${maxUsd}`,\n };\n }\n if (totalUsd >= maxUsd) {\n return {\n allowed: false,\n reason: \"cost_limit\",\n detail: `USD ${totalUsd.toFixed(6)} >= maxUsd ${maxUsd}`,\n };\n }\n return null;\n}\n\n/** The token-cap denial for the current state, or `null` if the token cap is satisfied. */\nfunction evaluateTokenCap(maxTokens: number | undefined, totalTokens: number): BudgetCheck | null {\n if (maxTokens !== undefined && totalTokens >= maxTokens) {\n return {\n allowed: false,\n reason: \"token_limit\",\n detail: `${totalTokens} >= maxTokens ${maxTokens}`,\n };\n }\n return null;\n}\n\n/**\n * Build a `BudgetTracker` that caps an agent run on tokens, on USD, or on both.\n *\n * ```ts\n * const agent = await Agent.create({\n * model: { id: \"openai/gpt-4o-mini\" },\n * budgetTracker: createUsdBudgetTracker({ maxUsd: 0.5 }),\n * });\n * ```\n *\n * The agent loop drives it: `track()` on every usage event, `nextIteration()` once per turn, and\n * `check()` before each turn — a denial halts the loop. Everything is per-instance and in-memory,\n * so one tracker is one run; reuse it across runs and the caps carry over.\n *\n * Three things a caller gets wrong here:\n *\n * - **`getTotal()` has no cost in it.** It returns `{ tokens, iterations }` only, and\n * `BudgetTotal.costUsd` is always `undefined` however much was spent. USD comes from the extra\n * `getTotalUsd()` on the returned object, which is `number | undefined` — `undefined` meaning\n * UNKNOWN, never zero.\n * - **Unknown cost is a one-way door.** The first event naming a model outside the pricing table\n * makes `getTotalUsd()` `undefined` for the rest of the run; a later priced event does not\n * restore it. Combined with `maxUsd`, that also denies every later `check()` — see\n * {@link UsdBudgetTrackerOptions.maxUsd}.\n * - **There is no iteration cap.** `iterations` is counted and reported, and nothing ever gates on\n * it. Use `createCounterBudgetTracker` from `@theokit/sdk` when you need `maxIterations`.\n *\n * `check()` evaluates the cost cap first, so a run breaching both caps reports `\"cost_limit\"`.\n * `track()` never throws and never rejects input — a non-finite or non-positive `tokens` is\n * discarded, silently.\n */\nexport function createUsdBudgetTracker(\n options: UsdBudgetTrackerOptions = {},\n): BudgetTracker & { nextIteration(): void; getTotalUsd(): number | undefined } {\n let totalTokens = 0;\n let iterations = 0;\n let totalUsd = 0;\n // Honest-null (D377): once any round's cost is UNKNOWN, the aggregate becomes\n // unknown and STAYS unknown — a later known round does not resurrect it.\n let costKnown = true;\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 const cost = computeUsdCost(pricing, event.model, event.type, t);\n if (cost === undefined) {\n costKnown = false; // poison — do NOT add 0 (that would be a dishonest $0)\n return;\n }\n if (costKnown) totalUsd += cost;\n },\n\n check(): BudgetCheck {\n // Cost cap is evaluated first (USD is the higher-signal denial for a human).\n return (\n evaluateCostCap(maxUsd, costKnown, totalUsd) ??\n evaluateTokenCap(maxTokens, totalTokens) ?? { allowed: true }\n );\n },\n\n getTotal(): BudgetTotal {\n return { tokens: totalTokens, iterations };\n },\n\n getTotalUsd(): number | undefined {\n return costKnown ? totalUsd : undefined;\n },\n\n nextIteration(): void {\n iterations += 1;\n },\n };\n}\n"]}
|