@theokit/sdk-budget 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +116 -0
- package/LICENSE +2 -2
- package/README.md +22 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +276 -24
- package/dist/index.d.ts +276 -24
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BudgetTracker } from '@theokit/sdk';
|
|
1
|
+
import { BudgetMode, BudgetSnapshot, BudgetHandle, BudgetOptions, TokenUsage, BudgetWindow, BudgetTracker } from '@theokit/sdk';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* M7-6 — honest-null cost render helper. Turns the `number | undefined` cost
|
|
@@ -38,9 +38,35 @@ declare function formatCostUsd(cost: number | undefined, opts?: FormatCostUsdOpt
|
|
|
38
38
|
*
|
|
39
39
|
* @internal
|
|
40
40
|
*/
|
|
41
|
+
/**
|
|
42
|
+
* Midnight UTC of `now`'s day — the start of a `1d` budget window.
|
|
43
|
+
*
|
|
44
|
+
* Always UTC, never the host's local timezone: "1 USD per day" resets at 00:00Z, so a budget in
|
|
45
|
+
* UTC-05:00 resets at 19:00 local. That is deliberate — a budget shared by processes in different
|
|
46
|
+
* regions must reset at one instant — but it surprises anyone reading a daily total at local
|
|
47
|
+
* midnight.
|
|
48
|
+
*/
|
|
41
49
|
declare function startOfDayUtc(now?: Date): Date;
|
|
50
|
+
/**
|
|
51
|
+
* Midnight UTC on the MONDAY of `now`'s week — the start of a `1w` budget window.
|
|
52
|
+
*
|
|
53
|
+
* ISO 8601, so the week starts on Monday, not Sunday. This is calendar-aligned rather than rolling:
|
|
54
|
+
* "5 USD per week" resets on Monday, which is what a person reading a weekly budget expects, and not
|
|
55
|
+
* a trailing 168 hours. `30d` and `365d` are relative for the mirror-image reason — nobody expects a
|
|
56
|
+
* monthly budget to reset on the 1st.
|
|
57
|
+
*/
|
|
42
58
|
declare function startOfWeekUtc(now?: Date): Date;
|
|
43
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Inclusive start timestamp (ms) of `window`, as `spentIn` uses it to decide which charges count.
|
|
61
|
+
*
|
|
62
|
+
* Two different behaviours behind one enum, and the difference is visible in every total:
|
|
63
|
+
*
|
|
64
|
+
* - `1d` / `1w` are CALENDAR-aligned to UTC (midnight; ISO Monday). Spend resets at a boundary.
|
|
65
|
+
* - `1h` / `30d` / `365d` are ROLLING — `now` minus the duration. Nothing ever "resets"; the
|
|
66
|
+
* oldest charges simply fall out of the window.
|
|
67
|
+
*
|
|
68
|
+
* Throws on a value outside `BudgetWindow`, which TypeScript already prevents.
|
|
69
|
+
*/
|
|
44
70
|
declare function windowStartMs(window: BudgetWindow, now?: Date): number;
|
|
45
71
|
|
|
46
72
|
/**
|
|
@@ -56,23 +82,47 @@ declare function windowStartMs(window: BudgetWindow, now?: Date): number;
|
|
|
56
82
|
* @internal
|
|
57
83
|
*/
|
|
58
84
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* handle those).
|
|
85
|
+
* Refuse an upcoming call that would push a `"block"`-mode budget past one of its limits. Call it
|
|
86
|
+
* BEFORE the LLM request, with your cost estimate for that request.
|
|
62
87
|
*
|
|
63
|
-
*
|
|
88
|
+
* Throws `BudgetExceededError` (carrying `budgetName`, `window`, the projected `spentUsd`, the
|
|
89
|
+
* `limitUsd` and `mode: "block"`) for the FIRST limit where `alreadySpent + estimatedUsd` exceeds
|
|
90
|
+
* the limit. The comparison is strict, so landing exactly on the limit is allowed through — while
|
|
91
|
+
* the post-charge `onExceed` callback fires at `>=`. The two boundaries do not agree.
|
|
92
|
+
*
|
|
93
|
+
* SILENTLY DOES NOTHING in three cases, and none of them is distinguishable from "you are within
|
|
94
|
+
* budget" at the call site:
|
|
95
|
+
*
|
|
96
|
+
* - `name` is not registered — a typo, or a budget deleted since. Nothing is enforced.
|
|
97
|
+
* - the budget's mode is `"warn"` or `"audit"` — those never block by design.
|
|
98
|
+
* - the estimate is low. Enforcement is only as good as `estimatedUsd`; passing 0 disables it.
|
|
64
99
|
*/
|
|
65
100
|
declare function preflightCheck(name: string, estimatedUsd: number): void;
|
|
66
101
|
/**
|
|
67
|
-
*
|
|
102
|
+
* Record the ACTUAL cost of a completed call against a budget and fire its callbacks. Call it after
|
|
103
|
+
* the LLM request, paired with `preflightCheck` before it.
|
|
104
|
+
*
|
|
105
|
+
* NEVER THROWS on budget state — including when the charge tips a limit. Enforcement happens on the
|
|
106
|
+
* next `preflightCheck`; this one only records and notifies. A callback that throws is caught and
|
|
107
|
+
* logged to stderr, so your `onThreshold` / `onExceed` cannot break the run either.
|
|
108
|
+
*
|
|
109
|
+
* Per mode:
|
|
110
|
+
*
|
|
111
|
+
* - `"audit"` — charge only, no callbacks.
|
|
112
|
+
* - `"warn"` — charge, then `onThreshold` at 80% or 95%, then `onExceed` at 100%. With no
|
|
113
|
+
* `onExceed` handler it writes a line to stderr instead.
|
|
68
114
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
115
|
+
* Per limit, exactly ONE callback fires per call: the highest matched threshold, or `onExceed`,
|
|
116
|
+
* never both. But there is NO de-duplication across calls — a budget sitting at 90% fires
|
|
117
|
+
* `onThreshold(0.8)` again on every single charge, and one past its limit fires `onExceed` on
|
|
118
|
+
* every charge forever. Debounce inside your handler if it pages anyone. A budget with three
|
|
119
|
+
* limits evaluates all three, so one call can fire three callbacks.
|
|
120
|
+
* - `"block"` — same callbacks as `"warn"`, still no throw. Concurrent sends each pass their own
|
|
121
|
+
* preflight, so the last to charge can legitimately tip a limit that preflight had cleared.
|
|
122
|
+
*
|
|
123
|
+
* A DELETED OR UNKNOWN BUDGET LOSES THE CHARGE. The function writes a warning to stderr and
|
|
124
|
+
* returns — the spend is not recorded anywhere, so a budget removed mid-flight under-reports rather
|
|
125
|
+
* than over-reports. This is the one path where the ledger and reality diverge on purpose.
|
|
76
126
|
*/
|
|
77
127
|
declare function chargeAndCheckThresholds(name: string, actualUsd: number): Promise<void>;
|
|
78
128
|
|
|
@@ -90,9 +140,40 @@ declare function chargeAndCheckThresholds(name: string, actualUsd: number): Prom
|
|
|
90
140
|
*
|
|
91
141
|
* @internal
|
|
92
142
|
*/
|
|
93
|
-
/**
|
|
143
|
+
/**
|
|
144
|
+
* Append `amountUsd` to the spend ledger for the budget called `name`, timestamped now.
|
|
145
|
+
*
|
|
146
|
+
* NOT idempotent. `withCwdMutex` SERIALIZES concurrent calls so two appends cannot interleave; it
|
|
147
|
+
* does not deduplicate them. Calling this twice with the same arguments records the spend twice,
|
|
148
|
+
* and there is no charge id to reconcile against — an at-least-once caller needs its own guard.
|
|
149
|
+
*
|
|
150
|
+
* NOT VALIDATED against the registry either: charging a name that was never passed to
|
|
151
|
+
* `createBudget` (a typo, say) succeeds silently and accumulates spend that no limit enforces and
|
|
152
|
+
* that `snapshotAll` — which iterates the REGISTRY — will never show.
|
|
153
|
+
*
|
|
154
|
+
* `chargeAndCheckThresholds` does not rescue you from that, and swapping to it trades one silent
|
|
155
|
+
* failure for another: on an unknown or already-deleted name it writes one line to stderr and
|
|
156
|
+
* RETURNS WITHOUT CHARGING, so the spend is lost rather than merely invisible. Neither function
|
|
157
|
+
* throws. Validate the name against `getBudgetOptionsRaw` / `getBudget` if it matters which of the
|
|
158
|
+
* two failures you get.
|
|
159
|
+
*
|
|
160
|
+
* `amountUsd <= 0` is a no-op, so a refund cannot be expressed here.
|
|
161
|
+
*
|
|
162
|
+
* The ledger is a MODULE-LEVEL SINGLETON: process-wide, shared by every budget and every agent in
|
|
163
|
+
* the process, and lost on exit — there is no persistence across restarts. Entries older than a
|
|
164
|
+
* year are garbage-collected opportunistically.
|
|
165
|
+
*/
|
|
94
166
|
declare function charge(name: string, amountUsd: number): Promise<void>;
|
|
95
|
-
/**
|
|
167
|
+
/**
|
|
168
|
+
* Total USD recorded for `name` inside `window`, summed at call time.
|
|
169
|
+
*
|
|
170
|
+
* Returns `0` for a name that was never charged — indistinguishable from a budget that exists and
|
|
171
|
+
* has spent nothing. Use `getBudget(name)` when you need to tell those apart.
|
|
172
|
+
*
|
|
173
|
+
* Lock-free: it reads the live array without taking the mutex, so a charge landing mid-read is
|
|
174
|
+
* simply included or not. Window boundaries are those of {@link windowStartMs} — `1d` and `1w` are
|
|
175
|
+
* UTC calendar-aligned, the rest are rolling.
|
|
176
|
+
*/
|
|
96
177
|
declare function spentIn(name: string, window: BudgetWindow, now?: Date): number;
|
|
97
178
|
|
|
98
179
|
/**
|
|
@@ -117,7 +198,34 @@ declare function spentIn(name: string, window: BudgetWindow, now?: Date): number
|
|
|
117
198
|
* @internal
|
|
118
199
|
*/
|
|
119
200
|
type ApiMode = "anthropic_messages" | "openai_chat_completions" | "openai_responses";
|
|
201
|
+
/**
|
|
202
|
+
* Which usage dialect a provider reports in, inferred from its name.
|
|
203
|
+
*
|
|
204
|
+
* Providers do not agree on the shape of a usage object: Anthropic reports `input_tokens` /
|
|
205
|
+
* `output_tokens`, OpenAI's Responses API reports yet another, and the large
|
|
206
|
+
* OpenAI-chat-compatible family (openai, openrouter, deepseek, google, ollama, lmstudio, …) shares
|
|
207
|
+
* one. This maps a provider name onto that choice.
|
|
208
|
+
*
|
|
209
|
+
* UNKNOWN NAMES FALL BACK to the OpenAI chat-completions shape, because that is what almost every
|
|
210
|
+
* compatible endpoint speaks — a new proxy usually works without a change here. Pass
|
|
211
|
+
* {@link normalizeUsage}'s `apiMode` explicitly when a provider is compatible in its wire format but
|
|
212
|
+
* not in its name.
|
|
213
|
+
*/
|
|
120
214
|
declare function inferApiMode(provider: string): ApiMode;
|
|
215
|
+
/**
|
|
216
|
+
* Turn a provider's raw usage object into the SDK's canonical {@link TokenUsage}.
|
|
217
|
+
*
|
|
218
|
+
* NEVER THROWS, and that is the point: usage arrives from a third party at the end of a successful
|
|
219
|
+
* call, so a missing or malformed field must not fail a request the model already answered. `null`,
|
|
220
|
+
* `undefined` and non-objects all yield an all-zero usage, and unrecognised fields are dropped.
|
|
221
|
+
*
|
|
222
|
+
* The cost of that tolerance is that a zero is ambiguous — it means "the provider reported nothing
|
|
223
|
+
* usable" as readily as "no tokens". A budget that suddenly stops accruing is the symptom of a
|
|
224
|
+
* dialect mismatch, not of free traffic.
|
|
225
|
+
*
|
|
226
|
+
* `apiMode` overrides the guess {@link inferApiMode} makes from the provider name; supply it for a
|
|
227
|
+
* compatible endpoint whose name is not recognised.
|
|
228
|
+
*/
|
|
121
229
|
declare function normalizeUsage(rawUsage: unknown, opts: {
|
|
122
230
|
provider: string;
|
|
123
231
|
apiMode?: ApiMode;
|
|
@@ -129,12 +237,81 @@ declare function normalizeUsage(rawUsage: unknown, opts: {
|
|
|
129
237
|
*
|
|
130
238
|
* @internal
|
|
131
239
|
*/
|
|
240
|
+
/**
|
|
241
|
+
* Register a budget under `opts.name` and return its live handle.
|
|
242
|
+
*
|
|
243
|
+
* ```ts
|
|
244
|
+
* const b = createBudget({ name: "daily", mode: "block", limits: [{ window: "1d", limitUsd: 5 }] });
|
|
245
|
+
* ```
|
|
246
|
+
*
|
|
247
|
+
* THROWS `ConfigurationError(code: "invalid_budget_name")` in two cases: a name that does not match
|
|
248
|
+
* `^[a-z0-9][a-z0-9_-]*$` (lowercase only — `"Daily"` and `"my.budget"` are rejected), and a name
|
|
249
|
+
* already registered. Duplicate registration is deliberately an error rather than an idempotent
|
|
250
|
+
* return, so a second `createBudget("daily", …)` with different limits cannot silently win.
|
|
251
|
+
*
|
|
252
|
+
* Registering does NOT reset spend. The ledger is keyed by NAME and outlives the registry entry, so
|
|
253
|
+
* re-creating a budget after `deleteBudget` inherits everything charged under that name — a
|
|
254
|
+
* config reload keeps enforcing the day's spend, which is usually right and is a surprise if you
|
|
255
|
+
* expected a fresh start.
|
|
256
|
+
*
|
|
257
|
+
* Process-local and non-persistent: nothing survives a restart, so re-create budgets at startup.
|
|
258
|
+
* `mode` defaults to `"warn"` (see {@link defaultMode}) — a budget created without one observes and
|
|
259
|
+
* does not block.
|
|
260
|
+
*/
|
|
132
261
|
declare function createBudget(opts: BudgetOptions): BudgetHandle;
|
|
262
|
+
/**
|
|
263
|
+
* The live handle for a registered budget, or `undefined` when the name is unknown.
|
|
264
|
+
*
|
|
265
|
+
* `undefined` is the honest answer for "never created" — it does NOT mean "zero spend". A budget
|
|
266
|
+
* that exists but has not been charged returns a handle whose `spentIn(...)` is 0, and the two are
|
|
267
|
+
* different facts: the first means nothing is enforcing a limit.
|
|
268
|
+
*
|
|
269
|
+
* The registry is per-PROCESS and holds no persistence, so a fresh process starts with no budgets
|
|
270
|
+
* and no ledger. Re-create them at startup.
|
|
271
|
+
*/
|
|
133
272
|
declare function getBudget(name: string): BudgetHandle | undefined;
|
|
273
|
+
/**
|
|
274
|
+
* Every budget registered in this process, in insertion order.
|
|
275
|
+
*
|
|
276
|
+
* Empty after a restart — see {@link getBudget} on process-local state. Use it to enumerate what is
|
|
277
|
+
* being enforced; use {@link snapshotAll} when you want the numbers rather than the handles.
|
|
278
|
+
*/
|
|
134
279
|
declare function listBudgets(): readonly BudgetHandle[];
|
|
280
|
+
/**
|
|
281
|
+
* Remove a budget from the registry. Returns `false` when the name was not registered.
|
|
282
|
+
*
|
|
283
|
+
* This stops ENFORCEMENT; it does not refund or clear the ledger. Re-creating a budget under the
|
|
284
|
+
* same name inherits the spend already recorded for that name, which is usually what you want after
|
|
285
|
+
* a config reload and a surprise if you were expecting a reset.
|
|
286
|
+
*/
|
|
135
287
|
declare function deleteBudget(name: string): boolean;
|
|
288
|
+
/**
|
|
289
|
+
* One row per budget PER WINDOW — a budget with three limits produces three rows, not one.
|
|
290
|
+
*
|
|
291
|
+
* `ratio` is `spentUsd / limitUsd`, and is 0 when the limit is 0 rather than `Infinity`, so a
|
|
292
|
+
* zero-limit budget does not poison a dashboard that sums or charts these. Spend is computed at call
|
|
293
|
+
* time from the ledger, counting only entries inside each window, so the same budget reports
|
|
294
|
+
* different numbers for `1d` and `30d`.
|
|
295
|
+
*/
|
|
136
296
|
declare function snapshotAll(): readonly BudgetSnapshot[];
|
|
297
|
+
/**
|
|
298
|
+
* The stored {@link BudgetOptions} exactly as registered, or `undefined` when the name is unknown.
|
|
299
|
+
*
|
|
300
|
+
* Distinct from {@link getBudget}, which returns a HANDLE with live accessors. Use this when you
|
|
301
|
+
* need the configuration itself — the limits array, the callbacks, the declared mode — for example
|
|
302
|
+
* to render it or to re-register the same shape elsewhere.
|
|
303
|
+
*
|
|
304
|
+
* The object is the registry's own, held by reference: mutating it changes what the budget enforces
|
|
305
|
+
* without going through `Budget.create`, which is a bug waiting to happen. Copy before editing.
|
|
306
|
+
*/
|
|
137
307
|
declare function getBudgetOptionsRaw(name: string): BudgetOptions | undefined;
|
|
308
|
+
/**
|
|
309
|
+
* The mode a budget enforces, resolving the omitted case to `"warn"`.
|
|
310
|
+
*
|
|
311
|
+
* `"warn"` fires the callbacks and lets the call through; `"block"` refuses it. The default is
|
|
312
|
+
* deliberate: a budget added for observability must not start rejecting traffic because someone
|
|
313
|
+
* forgot a field.
|
|
314
|
+
*/
|
|
138
315
|
declare function defaultMode(opts: BudgetOptions): BudgetMode;
|
|
139
316
|
|
|
140
317
|
/**
|
|
@@ -150,13 +327,36 @@ declare function defaultMode(opts: BudgetOptions): BudgetMode;
|
|
|
150
327
|
*
|
|
151
328
|
* @public
|
|
152
329
|
*/
|
|
330
|
+
/**
|
|
331
|
+
* The two rates needed to price one model, in USD per 1,000,000 tokens.
|
|
332
|
+
*
|
|
333
|
+
* List prices only — this package models no cached-input discount, no batch tier and no negotiated
|
|
334
|
+
* rate, so a cost computed here is an upper bound for anyone on a discount and simply wrong for a
|
|
335
|
+
* provider that bills per request.
|
|
336
|
+
*
|
|
337
|
+
* @public
|
|
338
|
+
*/
|
|
153
339
|
interface ModelPricing {
|
|
154
340
|
/** USD per 1,000,000 input tokens. */
|
|
155
341
|
readonly inputPerMillionUsd: number;
|
|
156
342
|
/** USD per 1,000,000 output tokens. */
|
|
157
343
|
readonly outputPerMillionUsd: number;
|
|
158
344
|
}
|
|
159
|
-
/**
|
|
345
|
+
/**
|
|
346
|
+
* The nine models this package prices out of the box, keyed by the exact `model` string a
|
|
347
|
+
* `BudgetUsageEvent` carries: four OpenAI, three Anthropic, two Google.
|
|
348
|
+
*
|
|
349
|
+
* Lookup is exact-match. No prefix stripping, no aliasing, no fuzzy fallback — `"gpt-4o"` and
|
|
350
|
+
* `"openrouter/openai/gpt-4o"` both MISS the `"openai/gpt-4o"` entry, and a miss means unknown
|
|
351
|
+
* cost, which under a `maxUsd` cap denies the run. Pass overrides through
|
|
352
|
+
* `createUsdBudgetTracker({ pricing })` rather than expecting a match.
|
|
353
|
+
*
|
|
354
|
+
* Prices are a dated snapshot (verified 2026-06) of public list prices and DRIFT — treat a total
|
|
355
|
+
* derived from them as an estimate, not as a bill. `Object.freeze` here is shallow: the map cannot
|
|
356
|
+
* gain keys, but the `ModelPricing` objects inside it are mutable.
|
|
357
|
+
*
|
|
358
|
+
* @public
|
|
359
|
+
*/
|
|
160
360
|
declare const BUILTIN_PRICING: Readonly<Record<string, ModelPricing>>;
|
|
161
361
|
/**
|
|
162
362
|
* Compute USD cost for a single token event.
|
|
@@ -197,19 +397,71 @@ declare function computeUsdCost(pricing: Readonly<Record<string, ModelPricing>>,
|
|
|
197
397
|
* @public
|
|
198
398
|
*/
|
|
199
399
|
|
|
200
|
-
/**
|
|
400
|
+
/**
|
|
401
|
+
* Options for {@link createUsdBudgetTracker}. Omitting all of them builds a tracker that counts and
|
|
402
|
+
* never denies.
|
|
403
|
+
*
|
|
404
|
+
* @public
|
|
405
|
+
*/
|
|
201
406
|
interface UsdBudgetTrackerOptions {
|
|
202
|
-
/**
|
|
407
|
+
/**
|
|
408
|
+
* Ceiling on total tokens, input and output combined. `check()` denies with
|
|
409
|
+
* `reason: "token_limit"` once the running total REACHES it (`>=`, not `>`).
|
|
410
|
+
*/
|
|
203
411
|
readonly maxTokens?: number;
|
|
204
|
-
/**
|
|
412
|
+
/**
|
|
413
|
+
* Ceiling on cumulative USD. `check()` denies with `reason: "cost_limit"` once the total reaches
|
|
414
|
+
* it.
|
|
415
|
+
*
|
|
416
|
+
* TRAP — setting this makes an UNPRICED MODEL DENY EVERYTHING. If any tracked event names a model
|
|
417
|
+
* absent from the pricing table, the total cost becomes permanently unknown, and a cap that
|
|
418
|
+
* cannot be verified fails CLOSED: every subsequent `check()` returns
|
|
419
|
+
* `{ allowed: false, reason: "cost_limit", detail: "cost unknown — …" }`. This is intentional
|
|
420
|
+
* (better than spending unbounded), but it means a model id that merely differs in spelling from
|
|
421
|
+
* a {@link BUILTIN_PRICING} key halts the agent. Supply `pricing` for anything not in that table,
|
|
422
|
+
* or leave `maxUsd` unset and gate on `maxTokens`.
|
|
423
|
+
*/
|
|
205
424
|
readonly maxUsd?: number;
|
|
206
|
-
/**
|
|
425
|
+
/**
|
|
426
|
+
* Per-model prices, spread OVER {@link BUILTIN_PRICING} — so an entry here replaces a built-in of
|
|
427
|
+
* the same key, and built-ins you do not name are kept.
|
|
428
|
+
*
|
|
429
|
+
* Keys are matched EXACTLY against `BudgetUsageEvent.model`. There is no prefix, alias or
|
|
430
|
+
* provider-stripping logic: `"gpt-4o"` does not match the built-in `"openai/gpt-4o"`. Use the
|
|
431
|
+
* same id string you pass to `Agent.create({ model })`.
|
|
432
|
+
*/
|
|
207
433
|
readonly pricing?: Readonly<Record<string, ModelPricing>>;
|
|
208
434
|
}
|
|
209
435
|
/**
|
|
210
|
-
* Build a
|
|
211
|
-
*
|
|
212
|
-
*
|
|
436
|
+
* Build a `BudgetTracker` that caps an agent run on tokens, on USD, or on both.
|
|
437
|
+
*
|
|
438
|
+
* ```ts
|
|
439
|
+
* const agent = await Agent.create({
|
|
440
|
+
* model: { id: "openai/gpt-4o-mini" },
|
|
441
|
+
* budgetTracker: createUsdBudgetTracker({ maxUsd: 0.5 }),
|
|
442
|
+
* });
|
|
443
|
+
* ```
|
|
444
|
+
*
|
|
445
|
+
* The agent loop drives it: `track()` on every usage event, `nextIteration()` once per turn, and
|
|
446
|
+
* `check()` before each turn — a denial halts the loop. Everything is per-instance and in-memory,
|
|
447
|
+
* so one tracker is one run; reuse it across runs and the caps carry over.
|
|
448
|
+
*
|
|
449
|
+
* Three things a caller gets wrong here:
|
|
450
|
+
*
|
|
451
|
+
* - **`getTotal()` has no cost in it.** It returns `{ tokens, iterations }` only, and
|
|
452
|
+
* `BudgetTotal.costUsd` is always `undefined` however much was spent. USD comes from the extra
|
|
453
|
+
* `getTotalUsd()` on the returned object, which is `number | undefined` — `undefined` meaning
|
|
454
|
+
* UNKNOWN, never zero.
|
|
455
|
+
* - **Unknown cost is a one-way door.** The first event naming a model outside the pricing table
|
|
456
|
+
* makes `getTotalUsd()` `undefined` for the rest of the run; a later priced event does not
|
|
457
|
+
* restore it. Combined with `maxUsd`, that also denies every later `check()` — see
|
|
458
|
+
* {@link UsdBudgetTrackerOptions.maxUsd}.
|
|
459
|
+
* - **There is no iteration cap.** `iterations` is counted and reported, and nothing ever gates on
|
|
460
|
+
* it. Use `createCounterBudgetTracker` from `@theokit/sdk` when you need `maxIterations`.
|
|
461
|
+
*
|
|
462
|
+
* `check()` evaluates the cost cap first, so a run breaching both caps reports `"cost_limit"`.
|
|
463
|
+
* `track()` never throws and never rejects input — a non-finite or non-positive `tokens` is
|
|
464
|
+
* discarded, silently.
|
|
213
465
|
*/
|
|
214
466
|
declare function createUsdBudgetTracker(options?: UsdBudgetTrackerOptions): BudgetTracker & {
|
|
215
467
|
nextIteration(): void;
|