@convex-dev/ai-budget 0.0.2-alpha.4 → 0.0.2-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -6
- package/dist/client/index.d.ts +318 -43
- package/dist/client/index.js +115 -68
- package/dist/component/_generated/component.d.ts +32 -0
- package/dist/component/lib.d.ts +73 -0
- package/dist/component/lib.js +304 -32
- package/dist/component/schema.d.ts +76 -3
- package/dist/component/schema.js +56 -2
- package/package.json +1 -1
- package/src/client/index.ts +199 -131
- package/src/component/_generated/component.ts +54 -3
- package/src/component/lib.test.ts +118 -0
- package/src/component/lib.ts +375 -48
- package/src/component/schema.ts +59 -2
package/README.md
CHANGED
|
@@ -26,7 +26,11 @@ full audit log you can replay later.
|
|
|
26
26
|
| **Usage & cost tracking** | Every request stored with messages, response, tokens, latency, and per-request cost. |
|
|
27
27
|
| **Attribution** | Each call is attributed to a `userId` **and** to the Convex action that made it — auto-detected via `ctx.meta`, no manual tagging. Running totals per user and per action. |
|
|
28
28
|
| **Tagged budgets** | `user` and `action` are just built-in *dimensions* — add your own (team, project, customer, env, feature…) by passing `tags`, and cap any of them with `ai.tag("customer").setLimits(...)`. One request can be billed to several buckets at once. |
|
|
29
|
-
| **Spend & token limits** | Per-
|
|
29
|
+
| **Spend & token limits** | Per-bucket **daily / monthly / lifetime** spend and token budgets, plus a requests-per-minute rate limit, a max-concurrent cap, and a block switch. |
|
|
30
|
+
| **Spend history** | Durable per-bucket **daily & monthly** rollups that survive request retention — real spend-over-time, "what did we spend last month," per user / action / tag. |
|
|
31
|
+
| **Approaching-limit alerts** | Set `warnAtPct` (e.g. 0.8) and get an `onThreshold` callback before a cap is hit; `onLimitReached` fires when one blocks. |
|
|
32
|
+
| **Manual credits/debits** | Comp a user or correct an overcharge with a signed adjustment; the change hits the live windows, the history, and an audit log. |
|
|
33
|
+
| **Cache-aware cost** | Cached (prompt-cache-read) tokens are billed at a discount using the gateway's real cached-token count — not the full input rate. |
|
|
30
34
|
| **Concurrency-safe caps** | A reserve-then-settle design makes admission a true atomic check — concurrent in-flight requests can't blow past the cap (a naive implementation overshoots ~40×). |
|
|
31
35
|
| **Hard or soft** | Each limit either **blocks** (`hard`) or **allows-with-a-warning** (`soft`). |
|
|
32
36
|
| **Per-feature budgets** | Cap or block a whole action (e.g. `summarize`) independently of any user. |
|
|
@@ -186,16 +190,21 @@ to the original. `lineage` walks the re-run chain in both directions.
|
|
|
186
190
|
ai.users.setLimits(ctx, {
|
|
187
191
|
userId,
|
|
188
192
|
requestsPerMinute?,
|
|
193
|
+
maxConcurrent?, // max in-flight requests at once
|
|
189
194
|
dailySpendLimitNanos?,
|
|
195
|
+
monthlySpendLimitNanos?, // calendar-month budget (UTC)
|
|
190
196
|
lifetimeSpendLimitNanos?,
|
|
191
197
|
dailyTokenLimit?,
|
|
198
|
+
monthlyTokenLimit?,
|
|
192
199
|
lifetimeTokenLimit?,
|
|
193
|
-
|
|
194
|
-
|
|
200
|
+
warnAtPct?, // e.g. 0.8 → fire onThreshold at 80% of a cap
|
|
201
|
+
enforcement?, // "hard" (block, default) | "soft" (warn but allow)
|
|
202
|
+
blocked?, // hard block on/off
|
|
195
203
|
})
|
|
196
204
|
ai.users.delete(ctx, { userId }) // remove a user and all their request rows
|
|
197
205
|
```
|
|
198
206
|
|
|
207
|
+
The same limit fields apply to `ai.actions.setLimits` and `ai.tag(d).setLimits`.
|
|
199
208
|
Pass a field as `undefined` to clear that limit (unlimited).
|
|
200
209
|
|
|
201
210
|
### Per-action budgets
|
|
@@ -246,6 +255,48 @@ Uncapped buckets never serialize, so adding tags you don't cap is free at
|
|
|
246
255
|
admission; their running totals still accrue for reporting. `ai.users.*` and
|
|
247
256
|
`ai.actions.*` are simply sugar over `ai.tag("user")` / `ai.tag("action")`.
|
|
248
257
|
|
|
258
|
+
Every dimension namespace (`users`, `actions`, `tag(d)`) shares the same methods:
|
|
259
|
+
`list`, `get`, `setLimits`, `bump`, `adjust`, `history`, `adjustments`, `delete`.
|
|
260
|
+
|
|
261
|
+
### Spend history (survives retention)
|
|
262
|
+
|
|
263
|
+
Request rows are retained only briefly (see retention), but **durable per-bucket
|
|
264
|
+
day/month rollups are not** — so charts and "what did we spend last month" keep
|
|
265
|
+
working:
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
await ai.users.history(ctx, { userId, period: "month" }); // [{ stamp, spendNanos, tokens, requests }]
|
|
269
|
+
await ai.tag("customer").history(ctx, { value: "acme", period: "day", limit: 30 });
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Approaching-limit alerts
|
|
273
|
+
|
|
274
|
+
Set a threshold (per bucket via `warnAtPct`, or a deployment default) and get a
|
|
275
|
+
callback before a cap is hit — plus one when a hard cap blocks:
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
await ai.global.setAlertDefaults(ctx, { warnAtPct: 0.8 }); // 80%, all buckets
|
|
279
|
+
await ai.users.setLimits(ctx, { userId, warnAtPct: 0.9 }); // override per bucket
|
|
280
|
+
|
|
281
|
+
new AIBudget(components.aiBudget, {
|
|
282
|
+
onThreshold: ({ userId, messages }) => notify(userId, messages), // approaching
|
|
283
|
+
onLimitReached: ({ userId, reason }) => notify(userId, reason), // blocked
|
|
284
|
+
onSoftLimit: ({ userId, messages }) => notify(userId, messages), // soft-cap exceeded
|
|
285
|
+
});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`chat()` also returns `notices` (approaching) alongside `warnings` (soft-exceeded).
|
|
289
|
+
|
|
290
|
+
### Manual credits & debits
|
|
291
|
+
|
|
292
|
+
Comp a user or correct an overcharge. Negative = credit, positive = extra charge;
|
|
293
|
+
it adjusts the live day/month/lifetime windows, the history, and an audit log:
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
await ai.users.adjust(ctx, { userId, deltaNanos: -5 * 1_000_000_000, reason: "goodwill" });
|
|
297
|
+
await ai.users.adjustments(ctx, { userId }); // the audit log
|
|
298
|
+
```
|
|
299
|
+
|
|
249
300
|
### Global (deployment-wide) budget
|
|
250
301
|
|
|
251
302
|
```ts
|
|
@@ -283,10 +334,18 @@ common models (validated against OpenRouter's public pricing); override or add
|
|
|
283
334
|
any model:
|
|
284
335
|
|
|
285
336
|
```ts
|
|
286
|
-
ai.prices.set(ctx, { model, inputNanosPerMTok, outputNanosPerMTok }) //
|
|
337
|
+
ai.prices.set(ctx, { model, inputNanosPerMTok, outputNanosPerMTok, cachedNanosPerMTok? }) // ≥ 0
|
|
287
338
|
ai.prices.list(ctx)
|
|
288
339
|
```
|
|
289
340
|
|
|
341
|
+
**Cached tokens & actual cost.** The gateway reports a real cached-token count
|
|
342
|
+
(`usage.inputTokenDetails.cacheReadTokens`), so the cached slice of a prompt is
|
|
343
|
+
billed at `cachedNanosPerMTok` (or a default 10%-of-input discount) instead of
|
|
344
|
+
the full input rate. The gateway does **not** currently return a dollar cost, so
|
|
345
|
+
cost is computed from tokens — but `finishRequest` accepts an authoritative
|
|
346
|
+
`costNanos` and prefers it whenever present, so adopting a real gateway cost is a
|
|
347
|
+
one-line change the day it's available.
|
|
348
|
+
|
|
290
349
|
**Real prices from an API.** The gateway's `provider/model` ids match
|
|
291
350
|
OpenRouter's, whose public models endpoint returns per-token pricing — so you can
|
|
292
351
|
keep prices current from your own action (this is app code; the component just
|
|
@@ -311,10 +370,12 @@ flagged `unpricedModel: true` so you know to add a real price.
|
|
|
311
370
|
### Observability
|
|
312
371
|
|
|
313
372
|
```ts
|
|
314
|
-
ai.users.list(ctx) // per-user spend today /
|
|
373
|
+
ai.users.list(ctx) // per-user spend today / month / total / limits
|
|
315
374
|
ai.actions.list(ctx) // per-action spend & totals
|
|
316
375
|
ai.tag("customer").list(ctx) // spend & caps for any custom dimension
|
|
317
|
-
ai.
|
|
376
|
+
ai.users.history(ctx, { userId, period: "month" }) // durable spend-over-time
|
|
377
|
+
ai.requests.list(ctx, { userId?, limit? }) // the audit log (blocked included)
|
|
378
|
+
ai.requests.list(ctx, { dimension: "customer", value: "acme" }) // filter the log by any tag
|
|
318
379
|
```
|
|
319
380
|
|
|
320
381
|
---
|
package/dist/client/index.d.ts
CHANGED
|
@@ -21,22 +21,34 @@ export type Tag = {
|
|
|
21
21
|
dimension: string;
|
|
22
22
|
value: string;
|
|
23
23
|
};
|
|
24
|
-
/**
|
|
25
|
-
export type
|
|
24
|
+
/** Common shape for budget-event callbacks. */
|
|
25
|
+
export type BudgetEventInfo = {
|
|
26
26
|
userId: string;
|
|
27
27
|
action?: string;
|
|
28
28
|
tags?: Tag[];
|
|
29
|
-
requestId
|
|
29
|
+
requestId?: string;
|
|
30
|
+
/** soft-cap warnings (onSoftLimit) or approaching-cap notices (onThreshold). */
|
|
31
|
+
messages: string[];
|
|
32
|
+
/** rejection code/reason (onLimitReached only). */
|
|
33
|
+
code?: string;
|
|
34
|
+
reason?: string;
|
|
35
|
+
};
|
|
36
|
+
/** @deprecated use BudgetEventInfo */
|
|
37
|
+
export type SoftLimitInfo = BudgetEventInfo & {
|
|
30
38
|
warnings: string[];
|
|
31
39
|
};
|
|
32
40
|
export type AIBudgetOptions = {
|
|
33
41
|
defaultModel?: string;
|
|
34
42
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
43
|
+
* A *soft* limit was exceeded (request still allowed). Lets you surface budget
|
|
44
|
+
* warnings even on the languageModel/Agent path where they can't be returned.
|
|
45
|
+
* Errors thrown in any of these callbacks are swallowed.
|
|
38
46
|
*/
|
|
39
47
|
onSoftLimit?: (info: SoftLimitInfo) => void | Promise<void>;
|
|
48
|
+
/** Usage crossed a bucket's warnAtPct threshold (approaching a cap). */
|
|
49
|
+
onThreshold?: (info: BudgetEventInfo) => void | Promise<void>;
|
|
50
|
+
/** A *hard* limit blocked the request (fires just before chat/model throws). */
|
|
51
|
+
onLimitReached?: (info: BudgetEventInfo) => void | Promise<void>;
|
|
40
52
|
};
|
|
41
53
|
type RunQueryCtx = {
|
|
42
54
|
runQuery: <Query extends FunctionReference<"query", "internal">>(query: Query, args: Query["_args"]) => Promise<Query["_returnType"]>;
|
|
@@ -67,13 +79,39 @@ export type ChatResult = {
|
|
|
67
79
|
cachedTokens: number;
|
|
68
80
|
/** Soft-limit warnings raised at admission (empty unless a soft cap was hit). */
|
|
69
81
|
warnings: string[];
|
|
82
|
+
/** Approaching-cap notices (empty unless a warnAtPct threshold was crossed). */
|
|
83
|
+
notices: string[];
|
|
84
|
+
};
|
|
85
|
+
/** Limits/controls settable on any budget bucket (user, action, or tag). */
|
|
86
|
+
export type BucketLimits = {
|
|
87
|
+
requestsPerMinute?: number;
|
|
88
|
+
maxConcurrent?: number;
|
|
89
|
+
dailySpendLimitNanos?: number;
|
|
90
|
+
monthlySpendLimitNanos?: number;
|
|
91
|
+
lifetimeSpendLimitNanos?: number;
|
|
92
|
+
dailyTokenLimit?: number;
|
|
93
|
+
monthlyTokenLimit?: number;
|
|
94
|
+
lifetimeTokenLimit?: number;
|
|
95
|
+
/** Fire an approaching-limit alert at this fraction of a cap (e.g. 0.8). */
|
|
96
|
+
warnAtPct?: number;
|
|
97
|
+
enforcement?: "hard" | "soft";
|
|
98
|
+
blocked?: boolean;
|
|
99
|
+
};
|
|
100
|
+
/** One-time bump amounts, added on top of a standing cap. */
|
|
101
|
+
export type BumpArgs = {
|
|
102
|
+
dailyNanos?: number;
|
|
103
|
+
monthlyNanos?: number;
|
|
104
|
+
lifetimeNanos?: number;
|
|
70
105
|
};
|
|
71
106
|
export declare class AIBudget {
|
|
72
107
|
component: AIBudgetApi;
|
|
73
108
|
defaultModel: string;
|
|
74
109
|
private onSoftLimit?;
|
|
110
|
+
private onThreshold?;
|
|
111
|
+
private onLimitReached?;
|
|
75
112
|
constructor(component: AIBudgetApi, options?: AIBudgetOptions);
|
|
76
|
-
private
|
|
113
|
+
private fireBudgetEvents;
|
|
114
|
+
private fireLimitReached;
|
|
77
115
|
/**
|
|
78
116
|
* One-shot chat through the AI Gateway with tracking + limits.
|
|
79
117
|
* Call from an action. `userId` defaults to the authenticated caller.
|
|
@@ -106,8 +144,11 @@ export declare class AIBudget {
|
|
|
106
144
|
private rerunImpl;
|
|
107
145
|
/** The request audit log, replay, and re-run lineage. */
|
|
108
146
|
get requests(): {
|
|
147
|
+
/** Filter by userId, or by any {dimension, value} (incl. custom tags). */
|
|
109
148
|
list: (ctx: RunQueryCtx, args?: {
|
|
110
149
|
userId?: string;
|
|
150
|
+
dimension?: string;
|
|
151
|
+
value?: string;
|
|
111
152
|
limit?: number;
|
|
112
153
|
}) => Promise<{
|
|
113
154
|
_id: string;
|
|
@@ -213,8 +254,8 @@ export declare class AIBudget {
|
|
|
213
254
|
* generalization of `users`/`actions`. Give it any dimension name (team,
|
|
214
255
|
* project, tenant, customer, env, feature, …) and set caps per value:
|
|
215
256
|
*
|
|
216
|
-
* ai.tag("customer").setLimits(ctx, { value: "acme",
|
|
217
|
-
* ai.tag("customer").
|
|
257
|
+
* ai.tag("customer").setLimits(ctx, { value: "acme", monthlySpendLimitNanos });
|
|
258
|
+
* ai.tag("customer").history(ctx, { value: "acme", period: "day" });
|
|
218
259
|
*
|
|
219
260
|
* Attribute a call to it by passing `tags` to `chat`/`languageModel`.
|
|
220
261
|
*/
|
|
@@ -222,22 +263,33 @@ export declare class AIBudget {
|
|
|
222
263
|
/** All buckets in this dimension. */
|
|
223
264
|
list: (ctx: RunQueryCtx) => Promise<{
|
|
224
265
|
spendTodayNanos: number;
|
|
266
|
+
spendThisMonthNanos: number;
|
|
225
267
|
_id: string;
|
|
226
268
|
_creationTime: number;
|
|
227
269
|
requestsPerMinute?: number | undefined;
|
|
270
|
+
maxConcurrent?: number | undefined;
|
|
228
271
|
dailySpendLimitNanos?: number | undefined;
|
|
272
|
+
monthlySpendLimitNanos?: number | undefined;
|
|
229
273
|
lifetimeSpendLimitNanos?: number | undefined;
|
|
230
274
|
dailyTokenLimit?: number | undefined;
|
|
275
|
+
monthlyTokenLimit?: number | undefined;
|
|
231
276
|
lifetimeTokenLimit?: number | undefined;
|
|
232
277
|
blocked?: boolean | undefined;
|
|
278
|
+
warnAtPct?: number | undefined;
|
|
233
279
|
enforcement?: "hard" | "soft" | undefined;
|
|
234
280
|
dailyBumpNanos?: number | undefined;
|
|
281
|
+
monthlyBumpNanos?: number | undefined;
|
|
235
282
|
lifetimeBumpNanos?: number | undefined;
|
|
236
283
|
bumpDayStamp?: string | undefined;
|
|
284
|
+
bumpMonthStamp?: string | undefined;
|
|
237
285
|
tokensToday?: number | undefined;
|
|
286
|
+
monthStamp?: string | undefined;
|
|
287
|
+
tokensThisMonth?: number | undefined;
|
|
238
288
|
reservedTodayNanos?: number | undefined;
|
|
289
|
+
reservedMonthNanos?: number | undefined;
|
|
239
290
|
reservedTotalNanos?: number | undefined;
|
|
240
291
|
reservedTodayTokens?: number | undefined;
|
|
292
|
+
reservedMonthTokens?: number | undefined;
|
|
241
293
|
reservedTotalTokens?: number | undefined;
|
|
242
294
|
pendingCount?: number | undefined;
|
|
243
295
|
dimension: string;
|
|
@@ -252,22 +304,33 @@ export declare class AIBudget {
|
|
|
252
304
|
value: string;
|
|
253
305
|
}) => Promise<{
|
|
254
306
|
spendTodayNanos: number;
|
|
307
|
+
spendThisMonthNanos: number;
|
|
255
308
|
_id: string;
|
|
256
309
|
_creationTime: number;
|
|
257
310
|
requestsPerMinute?: number | undefined;
|
|
311
|
+
maxConcurrent?: number | undefined;
|
|
258
312
|
dailySpendLimitNanos?: number | undefined;
|
|
313
|
+
monthlySpendLimitNanos?: number | undefined;
|
|
259
314
|
lifetimeSpendLimitNanos?: number | undefined;
|
|
260
315
|
dailyTokenLimit?: number | undefined;
|
|
316
|
+
monthlyTokenLimit?: number | undefined;
|
|
261
317
|
lifetimeTokenLimit?: number | undefined;
|
|
262
318
|
blocked?: boolean | undefined;
|
|
319
|
+
warnAtPct?: number | undefined;
|
|
263
320
|
enforcement?: "hard" | "soft" | undefined;
|
|
264
321
|
dailyBumpNanos?: number | undefined;
|
|
322
|
+
monthlyBumpNanos?: number | undefined;
|
|
265
323
|
lifetimeBumpNanos?: number | undefined;
|
|
266
324
|
bumpDayStamp?: string | undefined;
|
|
325
|
+
bumpMonthStamp?: string | undefined;
|
|
267
326
|
tokensToday?: number | undefined;
|
|
327
|
+
monthStamp?: string | undefined;
|
|
328
|
+
tokensThisMonth?: number | undefined;
|
|
268
329
|
reservedTodayNanos?: number | undefined;
|
|
330
|
+
reservedMonthNanos?: number | undefined;
|
|
269
331
|
reservedTotalNanos?: number | undefined;
|
|
270
332
|
reservedTodayTokens?: number | undefined;
|
|
333
|
+
reservedMonthTokens?: number | undefined;
|
|
271
334
|
reservedTotalTokens?: number | undefined;
|
|
272
335
|
pendingCount?: number | undefined;
|
|
273
336
|
dimension: string;
|
|
@@ -279,20 +342,50 @@ export declare class AIBudget {
|
|
|
279
342
|
} | null>;
|
|
280
343
|
setLimits: (ctx: RunMutationCtx, args: {
|
|
281
344
|
value: string;
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
lifetimeSpendLimitNanos?: number;
|
|
285
|
-
dailyTokenLimit?: number;
|
|
286
|
-
lifetimeTokenLimit?: number;
|
|
287
|
-
enforcement?: "hard" | "soft";
|
|
288
|
-
blocked?: boolean;
|
|
289
|
-
}) => Promise<null>;
|
|
290
|
-
/** One-time "approve another $X" bump (daily is today-only). */
|
|
345
|
+
} & BucketLimits) => Promise<null>;
|
|
346
|
+
/** One-time "approve another $X" bump (daily/monthly reset with the window). */
|
|
291
347
|
bump: (ctx: RunMutationCtx, args: {
|
|
292
348
|
value: string;
|
|
293
|
-
|
|
294
|
-
|
|
349
|
+
} & BumpArgs) => Promise<null>;
|
|
350
|
+
/** Manually credit (negative) or debit (positive) this bucket. */
|
|
351
|
+
adjust: (ctx: RunMutationCtx, args: {
|
|
352
|
+
value: string;
|
|
353
|
+
} & {
|
|
354
|
+
deltaNanos: number;
|
|
355
|
+
tokens?: number;
|
|
356
|
+
reason?: string;
|
|
295
357
|
}) => Promise<null>;
|
|
358
|
+
/** Durable spend history for this bucket (per day or per month). */
|
|
359
|
+
history: (ctx: RunQueryCtx, args: {
|
|
360
|
+
value: string;
|
|
361
|
+
} & {
|
|
362
|
+
period?: "day" | "month";
|
|
363
|
+
limit?: number;
|
|
364
|
+
}) => Promise<{
|
|
365
|
+
_id: string;
|
|
366
|
+
_creationTime: number;
|
|
367
|
+
dimension: string;
|
|
368
|
+
value: string;
|
|
369
|
+
period: "day" | "month";
|
|
370
|
+
stamp: string;
|
|
371
|
+
spendNanos: number;
|
|
372
|
+
tokens: number;
|
|
373
|
+
requests: number;
|
|
374
|
+
}[]>;
|
|
375
|
+
/** Manual-adjustment audit log for this bucket. */
|
|
376
|
+
adjustments: (ctx: RunQueryCtx, args: {
|
|
377
|
+
value: string;
|
|
378
|
+
} & {
|
|
379
|
+
limit?: number;
|
|
380
|
+
}) => Promise<{
|
|
381
|
+
_id: string;
|
|
382
|
+
_creationTime: number;
|
|
383
|
+
tokens?: number | undefined;
|
|
384
|
+
reason?: string | undefined;
|
|
385
|
+
dimension: string;
|
|
386
|
+
value: string;
|
|
387
|
+
deltaNanos: number;
|
|
388
|
+
}[]>;
|
|
296
389
|
/** Delete the bucket (for "user", also its request rows). */
|
|
297
390
|
delete: (ctx: RunMutationCtx, args: {
|
|
298
391
|
value: string;
|
|
@@ -301,26 +394,39 @@ export declare class AIBudget {
|
|
|
301
394
|
done: boolean;
|
|
302
395
|
}>;
|
|
303
396
|
};
|
|
397
|
+
private dimensionApi;
|
|
304
398
|
/** Per-user budgets and controls — sugar over the "user" dimension. */
|
|
305
399
|
get users(): {
|
|
400
|
+
/** All buckets in this dimension. */
|
|
306
401
|
list: (ctx: RunQueryCtx) => Promise<{
|
|
307
402
|
spendTodayNanos: number;
|
|
403
|
+
spendThisMonthNanos: number;
|
|
308
404
|
_id: string;
|
|
309
405
|
_creationTime: number;
|
|
310
406
|
requestsPerMinute?: number | undefined;
|
|
407
|
+
maxConcurrent?: number | undefined;
|
|
311
408
|
dailySpendLimitNanos?: number | undefined;
|
|
409
|
+
monthlySpendLimitNanos?: number | undefined;
|
|
312
410
|
lifetimeSpendLimitNanos?: number | undefined;
|
|
313
411
|
dailyTokenLimit?: number | undefined;
|
|
412
|
+
monthlyTokenLimit?: number | undefined;
|
|
314
413
|
lifetimeTokenLimit?: number | undefined;
|
|
315
414
|
blocked?: boolean | undefined;
|
|
415
|
+
warnAtPct?: number | undefined;
|
|
316
416
|
enforcement?: "hard" | "soft" | undefined;
|
|
317
417
|
dailyBumpNanos?: number | undefined;
|
|
418
|
+
monthlyBumpNanos?: number | undefined;
|
|
318
419
|
lifetimeBumpNanos?: number | undefined;
|
|
319
420
|
bumpDayStamp?: string | undefined;
|
|
421
|
+
bumpMonthStamp?: string | undefined;
|
|
320
422
|
tokensToday?: number | undefined;
|
|
423
|
+
monthStamp?: string | undefined;
|
|
424
|
+
tokensThisMonth?: number | undefined;
|
|
321
425
|
reservedTodayNanos?: number | undefined;
|
|
426
|
+
reservedMonthNanos?: number | undefined;
|
|
322
427
|
reservedTotalNanos?: number | undefined;
|
|
323
428
|
reservedTodayTokens?: number | undefined;
|
|
429
|
+
reservedMonthTokens?: number | undefined;
|
|
324
430
|
reservedTotalTokens?: number | undefined;
|
|
325
431
|
pendingCount?: number | undefined;
|
|
326
432
|
dimension: string;
|
|
@@ -330,23 +436,94 @@ export declare class AIBudget {
|
|
|
330
436
|
totalTokens: number;
|
|
331
437
|
dayStamp: string;
|
|
332
438
|
}[]>;
|
|
439
|
+
/** One bucket's limits + spend (null if it has none yet). */
|
|
440
|
+
get: (ctx: RunQueryCtx, args: {
|
|
441
|
+
userId: string;
|
|
442
|
+
}) => Promise<{
|
|
443
|
+
spendTodayNanos: number;
|
|
444
|
+
spendThisMonthNanos: number;
|
|
445
|
+
_id: string;
|
|
446
|
+
_creationTime: number;
|
|
447
|
+
requestsPerMinute?: number | undefined;
|
|
448
|
+
maxConcurrent?: number | undefined;
|
|
449
|
+
dailySpendLimitNanos?: number | undefined;
|
|
450
|
+
monthlySpendLimitNanos?: number | undefined;
|
|
451
|
+
lifetimeSpendLimitNanos?: number | undefined;
|
|
452
|
+
dailyTokenLimit?: number | undefined;
|
|
453
|
+
monthlyTokenLimit?: number | undefined;
|
|
454
|
+
lifetimeTokenLimit?: number | undefined;
|
|
455
|
+
blocked?: boolean | undefined;
|
|
456
|
+
warnAtPct?: number | undefined;
|
|
457
|
+
enforcement?: "hard" | "soft" | undefined;
|
|
458
|
+
dailyBumpNanos?: number | undefined;
|
|
459
|
+
monthlyBumpNanos?: number | undefined;
|
|
460
|
+
lifetimeBumpNanos?: number | undefined;
|
|
461
|
+
bumpDayStamp?: string | undefined;
|
|
462
|
+
bumpMonthStamp?: string | undefined;
|
|
463
|
+
tokensToday?: number | undefined;
|
|
464
|
+
monthStamp?: string | undefined;
|
|
465
|
+
tokensThisMonth?: number | undefined;
|
|
466
|
+
reservedTodayNanos?: number | undefined;
|
|
467
|
+
reservedMonthNanos?: number | undefined;
|
|
468
|
+
reservedTotalNanos?: number | undefined;
|
|
469
|
+
reservedTodayTokens?: number | undefined;
|
|
470
|
+
reservedMonthTokens?: number | undefined;
|
|
471
|
+
reservedTotalTokens?: number | undefined;
|
|
472
|
+
pendingCount?: number | undefined;
|
|
473
|
+
dimension: string;
|
|
474
|
+
value: string;
|
|
475
|
+
totalSpendNanos: number;
|
|
476
|
+
totalRequests: number;
|
|
477
|
+
totalTokens: number;
|
|
478
|
+
dayStamp: string;
|
|
479
|
+
} | null>;
|
|
333
480
|
setLimits: (ctx: RunMutationCtx, args: {
|
|
334
481
|
userId: string;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
lifetimeSpendLimitNanos?: number;
|
|
338
|
-
dailyTokenLimit?: number;
|
|
339
|
-
lifetimeTokenLimit?: number;
|
|
340
|
-
enforcement?: "hard" | "soft";
|
|
341
|
-
blocked?: boolean;
|
|
342
|
-
}) => Promise<null>;
|
|
343
|
-
/** One-time "approve another $X" bump (daily is today-only). */
|
|
482
|
+
} & BucketLimits) => Promise<null>;
|
|
483
|
+
/** One-time "approve another $X" bump (daily/monthly reset with the window). */
|
|
344
484
|
bump: (ctx: RunMutationCtx, args: {
|
|
345
485
|
userId: string;
|
|
346
|
-
|
|
347
|
-
|
|
486
|
+
} & BumpArgs) => Promise<null>;
|
|
487
|
+
/** Manually credit (negative) or debit (positive) this bucket. */
|
|
488
|
+
adjust: (ctx: RunMutationCtx, args: {
|
|
489
|
+
userId: string;
|
|
490
|
+
} & {
|
|
491
|
+
deltaNanos: number;
|
|
492
|
+
tokens?: number;
|
|
493
|
+
reason?: string;
|
|
348
494
|
}) => Promise<null>;
|
|
349
|
-
/**
|
|
495
|
+
/** Durable spend history for this bucket (per day or per month). */
|
|
496
|
+
history: (ctx: RunQueryCtx, args: {
|
|
497
|
+
userId: string;
|
|
498
|
+
} & {
|
|
499
|
+
period?: "day" | "month";
|
|
500
|
+
limit?: number;
|
|
501
|
+
}) => Promise<{
|
|
502
|
+
_id: string;
|
|
503
|
+
_creationTime: number;
|
|
504
|
+
dimension: string;
|
|
505
|
+
value: string;
|
|
506
|
+
period: "day" | "month";
|
|
507
|
+
stamp: string;
|
|
508
|
+
spendNanos: number;
|
|
509
|
+
tokens: number;
|
|
510
|
+
requests: number;
|
|
511
|
+
}[]>;
|
|
512
|
+
/** Manual-adjustment audit log for this bucket. */
|
|
513
|
+
adjustments: (ctx: RunQueryCtx, args: {
|
|
514
|
+
userId: string;
|
|
515
|
+
} & {
|
|
516
|
+
limit?: number;
|
|
517
|
+
}) => Promise<{
|
|
518
|
+
_id: string;
|
|
519
|
+
_creationTime: number;
|
|
520
|
+
tokens?: number | undefined;
|
|
521
|
+
reason?: string | undefined;
|
|
522
|
+
dimension: string;
|
|
523
|
+
value: string;
|
|
524
|
+
deltaNanos: number;
|
|
525
|
+
}[]>;
|
|
526
|
+
/** Delete the bucket (for "user", also its request rows). */
|
|
350
527
|
delete: (ctx: RunMutationCtx, args: {
|
|
351
528
|
userId: string;
|
|
352
529
|
}) => Promise<{
|
|
@@ -356,24 +533,36 @@ export declare class AIBudget {
|
|
|
356
533
|
};
|
|
357
534
|
/** Per-action (per-feature) budgets — sugar over the "action" dimension. */
|
|
358
535
|
get actions(): {
|
|
536
|
+
/** All buckets in this dimension. */
|
|
359
537
|
list: (ctx: RunQueryCtx) => Promise<{
|
|
360
538
|
spendTodayNanos: number;
|
|
539
|
+
spendThisMonthNanos: number;
|
|
361
540
|
_id: string;
|
|
362
541
|
_creationTime: number;
|
|
363
542
|
requestsPerMinute?: number | undefined;
|
|
543
|
+
maxConcurrent?: number | undefined;
|
|
364
544
|
dailySpendLimitNanos?: number | undefined;
|
|
545
|
+
monthlySpendLimitNanos?: number | undefined;
|
|
365
546
|
lifetimeSpendLimitNanos?: number | undefined;
|
|
366
547
|
dailyTokenLimit?: number | undefined;
|
|
548
|
+
monthlyTokenLimit?: number | undefined;
|
|
367
549
|
lifetimeTokenLimit?: number | undefined;
|
|
368
550
|
blocked?: boolean | undefined;
|
|
551
|
+
warnAtPct?: number | undefined;
|
|
369
552
|
enforcement?: "hard" | "soft" | undefined;
|
|
370
553
|
dailyBumpNanos?: number | undefined;
|
|
554
|
+
monthlyBumpNanos?: number | undefined;
|
|
371
555
|
lifetimeBumpNanos?: number | undefined;
|
|
372
556
|
bumpDayStamp?: string | undefined;
|
|
557
|
+
bumpMonthStamp?: string | undefined;
|
|
373
558
|
tokensToday?: number | undefined;
|
|
559
|
+
monthStamp?: string | undefined;
|
|
560
|
+
tokensThisMonth?: number | undefined;
|
|
374
561
|
reservedTodayNanos?: number | undefined;
|
|
562
|
+
reservedMonthNanos?: number | undefined;
|
|
375
563
|
reservedTotalNanos?: number | undefined;
|
|
376
564
|
reservedTodayTokens?: number | undefined;
|
|
565
|
+
reservedMonthTokens?: number | undefined;
|
|
377
566
|
reservedTotalTokens?: number | undefined;
|
|
378
567
|
pendingCount?: number | undefined;
|
|
379
568
|
dimension: string;
|
|
@@ -383,23 +572,102 @@ export declare class AIBudget {
|
|
|
383
572
|
totalTokens: number;
|
|
384
573
|
dayStamp: string;
|
|
385
574
|
}[]>;
|
|
575
|
+
/** One bucket's limits + spend (null if it has none yet). */
|
|
576
|
+
get: (ctx: RunQueryCtx, args: {
|
|
577
|
+
name: string;
|
|
578
|
+
}) => Promise<{
|
|
579
|
+
spendTodayNanos: number;
|
|
580
|
+
spendThisMonthNanos: number;
|
|
581
|
+
_id: string;
|
|
582
|
+
_creationTime: number;
|
|
583
|
+
requestsPerMinute?: number | undefined;
|
|
584
|
+
maxConcurrent?: number | undefined;
|
|
585
|
+
dailySpendLimitNanos?: number | undefined;
|
|
586
|
+
monthlySpendLimitNanos?: number | undefined;
|
|
587
|
+
lifetimeSpendLimitNanos?: number | undefined;
|
|
588
|
+
dailyTokenLimit?: number | undefined;
|
|
589
|
+
monthlyTokenLimit?: number | undefined;
|
|
590
|
+
lifetimeTokenLimit?: number | undefined;
|
|
591
|
+
blocked?: boolean | undefined;
|
|
592
|
+
warnAtPct?: number | undefined;
|
|
593
|
+
enforcement?: "hard" | "soft" | undefined;
|
|
594
|
+
dailyBumpNanos?: number | undefined;
|
|
595
|
+
monthlyBumpNanos?: number | undefined;
|
|
596
|
+
lifetimeBumpNanos?: number | undefined;
|
|
597
|
+
bumpDayStamp?: string | undefined;
|
|
598
|
+
bumpMonthStamp?: string | undefined;
|
|
599
|
+
tokensToday?: number | undefined;
|
|
600
|
+
monthStamp?: string | undefined;
|
|
601
|
+
tokensThisMonth?: number | undefined;
|
|
602
|
+
reservedTodayNanos?: number | undefined;
|
|
603
|
+
reservedMonthNanos?: number | undefined;
|
|
604
|
+
reservedTotalNanos?: number | undefined;
|
|
605
|
+
reservedTodayTokens?: number | undefined;
|
|
606
|
+
reservedMonthTokens?: number | undefined;
|
|
607
|
+
reservedTotalTokens?: number | undefined;
|
|
608
|
+
pendingCount?: number | undefined;
|
|
609
|
+
dimension: string;
|
|
610
|
+
value: string;
|
|
611
|
+
totalSpendNanos: number;
|
|
612
|
+
totalRequests: number;
|
|
613
|
+
totalTokens: number;
|
|
614
|
+
dayStamp: string;
|
|
615
|
+
} | null>;
|
|
386
616
|
setLimits: (ctx: RunMutationCtx, args: {
|
|
387
617
|
name: string;
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
dailyTokenLimit?: number;
|
|
391
|
-
lifetimeTokenLimit?: number;
|
|
392
|
-
enforcement?: "hard" | "soft";
|
|
393
|
-
/** Hard-block this action (was `disabled`). */
|
|
394
|
-
blocked?: boolean;
|
|
395
|
-
}) => Promise<null>;
|
|
618
|
+
} & BucketLimits) => Promise<null>;
|
|
619
|
+
/** One-time "approve another $X" bump (daily/monthly reset with the window). */
|
|
396
620
|
bump: (ctx: RunMutationCtx, args: {
|
|
397
621
|
name: string;
|
|
398
|
-
|
|
399
|
-
|
|
622
|
+
} & BumpArgs) => Promise<null>;
|
|
623
|
+
/** Manually credit (negative) or debit (positive) this bucket. */
|
|
624
|
+
adjust: (ctx: RunMutationCtx, args: {
|
|
625
|
+
name: string;
|
|
626
|
+
} & {
|
|
627
|
+
deltaNanos: number;
|
|
628
|
+
tokens?: number;
|
|
629
|
+
reason?: string;
|
|
400
630
|
}) => Promise<null>;
|
|
631
|
+
/** Durable spend history for this bucket (per day or per month). */
|
|
632
|
+
history: (ctx: RunQueryCtx, args: {
|
|
633
|
+
name: string;
|
|
634
|
+
} & {
|
|
635
|
+
period?: "day" | "month";
|
|
636
|
+
limit?: number;
|
|
637
|
+
}) => Promise<{
|
|
638
|
+
_id: string;
|
|
639
|
+
_creationTime: number;
|
|
640
|
+
dimension: string;
|
|
641
|
+
value: string;
|
|
642
|
+
period: "day" | "month";
|
|
643
|
+
stamp: string;
|
|
644
|
+
spendNanos: number;
|
|
645
|
+
tokens: number;
|
|
646
|
+
requests: number;
|
|
647
|
+
}[]>;
|
|
648
|
+
/** Manual-adjustment audit log for this bucket. */
|
|
649
|
+
adjustments: (ctx: RunQueryCtx, args: {
|
|
650
|
+
name: string;
|
|
651
|
+
} & {
|
|
652
|
+
limit?: number;
|
|
653
|
+
}) => Promise<{
|
|
654
|
+
_id: string;
|
|
655
|
+
_creationTime: number;
|
|
656
|
+
tokens?: number | undefined;
|
|
657
|
+
reason?: string | undefined;
|
|
658
|
+
dimension: string;
|
|
659
|
+
value: string;
|
|
660
|
+
deltaNanos: number;
|
|
661
|
+
}[]>;
|
|
662
|
+
/** Delete the bucket (for "user", also its request rows). */
|
|
663
|
+
delete: (ctx: RunMutationCtx, args: {
|
|
664
|
+
name: string;
|
|
665
|
+
}) => Promise<{
|
|
666
|
+
deletedThisBatch: number;
|
|
667
|
+
done: boolean;
|
|
668
|
+
}>;
|
|
401
669
|
};
|
|
402
|
-
/** The deployment-wide budget and retention config. */
|
|
670
|
+
/** The deployment-wide budget, alerts, and retention config. */
|
|
403
671
|
get global(): {
|
|
404
672
|
/** Limits + spend today/total. */
|
|
405
673
|
status: (ctx: RunQueryCtx) => Promise<{
|
|
@@ -419,6 +687,10 @@ export declare class AIBudget {
|
|
|
419
687
|
dailyNanos?: number;
|
|
420
688
|
lifetimeNanos?: number;
|
|
421
689
|
}) => Promise<null>;
|
|
690
|
+
/** Default approaching-limit alert threshold (fraction of a cap, e.g. 0.8). */
|
|
691
|
+
setAlertDefaults: (ctx: RunMutationCtx, args: {
|
|
692
|
+
warnAtPct?: number;
|
|
693
|
+
}) => Promise<null>;
|
|
422
694
|
/** Request-row retention window in ms (default 1h; 0 disables). */
|
|
423
695
|
setRetention: (ctx: RunMutationCtx, args: {
|
|
424
696
|
retentionMs: number;
|
|
@@ -442,6 +714,7 @@ export declare class AIBudget {
|
|
|
442
714
|
[x: string]: {
|
|
443
715
|
input: number;
|
|
444
716
|
output: number;
|
|
717
|
+
cached?: number | undefined;
|
|
445
718
|
overridden: boolean;
|
|
446
719
|
};
|
|
447
720
|
}>;
|
|
@@ -449,6 +722,8 @@ export declare class AIBudget {
|
|
|
449
722
|
model: string;
|
|
450
723
|
inputNanosPerMTok: number;
|
|
451
724
|
outputNanosPerMTok: number;
|
|
725
|
+
/** Cache-read rate; defaults to a discount off input if omitted. */
|
|
726
|
+
cachedNanosPerMTok?: number;
|
|
452
727
|
}) => Promise<null>;
|
|
453
728
|
};
|
|
454
729
|
}
|