@convex-dev/ai-budget 0.0.2-alpha.0 → 0.0.2-alpha.10

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.
@@ -1,4 +1,4 @@
1
- import type { Expand, FunctionReference } from "convex/server";
1
+ import { type Expand, type FunctionReference, type HttpRouter } from "convex/server";
2
2
  import { type GenericId } from "convex/values";
3
3
  import { type LanguageModel } from "ai";
4
4
  import type { api } from "../component/_generated/api";
@@ -11,21 +11,44 @@ type UseApi<API> = Expand<{
11
11
  export type AIBudgetApi = UseApi<typeof api>;
12
12
  /** @deprecated use AIBudgetApi */
13
13
  export type AIGatewayApi = AIBudgetApi;
14
- /** Fired when a request is admitted over a *soft* limit. */
15
- export type SoftLimitInfo = {
14
+ /**
15
+ * One attribution tag: a (dimension, value) pair, e.g. {dimension:"customer",
16
+ * value:"acme"}. `user` and `action` are built-in dimensions (set via
17
+ * userId/action); use tags for anything else — team, project, tenant, env, ….
18
+ * Any tagged bucket can carry its own budget (see `ai.tag(dimension)`).
19
+ */
20
+ export type Tag = {
21
+ dimension: string;
22
+ value: string;
23
+ };
24
+ /** Common shape for budget-event callbacks. */
25
+ export type BudgetEventInfo = {
16
26
  userId: string;
17
27
  action?: string;
18
- requestId: string;
28
+ tags?: Tag[];
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 & {
19
38
  warnings: string[];
20
39
  };
21
40
  export type AIBudgetOptions = {
22
41
  defaultModel?: string;
23
42
  /**
24
- * Called when a soft limit is exceeded (the request is still allowed). Lets
25
- * you surface budget warnings even on the languageModel/Agent path, where
26
- * they can't be returned. Errors thrown here are swallowed.
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.
27
46
  */
28
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>;
29
52
  };
30
53
  type RunQueryCtx = {
31
54
  runQuery: <Query extends FunctionReference<"query", "internal">>(query: Query, args: Query["_args"]) => Promise<Query["_returnType"]>;
@@ -56,13 +79,39 @@ export type ChatResult = {
56
79
  cachedTokens: number;
57
80
  /** Soft-limit warnings raised at admission (empty unless a soft cap was hit). */
58
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;
59
105
  };
60
106
  export declare class AIBudget {
61
107
  component: AIBudgetApi;
62
108
  defaultModel: string;
63
109
  private onSoftLimit?;
110
+ private onThreshold?;
111
+ private onLimitReached?;
64
112
  constructor(component: AIBudgetApi, options?: AIBudgetOptions);
65
- private fireSoftLimit;
113
+ private fireBudgetEvents;
114
+ private fireLimitReached;
66
115
  /**
67
116
  * One-shot chat through the AI Gateway with tracking + limits.
68
117
  * Call from an action. `userId` defaults to the authenticated caller.
@@ -76,6 +125,8 @@ export declare class AIBudget {
76
125
  rerunOf?: string;
77
126
  /** Attribute spend to this action name. Defaults to the calling Convex action. */
78
127
  action?: string;
128
+ /** Extra attribution dimensions to bill/limit (team, customer, env, …). */
129
+ tags?: Tag[];
79
130
  }): Promise<ChatResult>;
80
131
  /**
81
132
  * An AI SDK LanguageModel that enforces limits and records usage/cost for
@@ -87,17 +138,26 @@ export declare class AIBudget {
87
138
  userId?: string;
88
139
  model?: string;
89
140
  action?: string;
141
+ /** Extra attribution dimensions to bill/limit (team, customer, env, …). */
142
+ tags?: Tag[];
90
143
  }): LanguageModel;
91
144
  private rerunImpl;
92
145
  /** The request audit log, replay, and re-run lineage. */
93
146
  get requests(): {
147
+ /** Filter by userId, or by any {dimension, value} (incl. custom tags). */
94
148
  list: (ctx: RunQueryCtx, args?: {
95
149
  userId?: string;
150
+ dimension?: string;
151
+ value?: string;
96
152
  limit?: number;
97
153
  }) => Promise<{
98
154
  _id: string;
99
155
  _creationTime: number;
100
156
  actionName?: string | undefined;
157
+ tags?: {
158
+ dimension: string;
159
+ value: string;
160
+ }[] | undefined;
101
161
  estimatedNanos?: number | undefined;
102
162
  estimatedTokens?: number | undefined;
103
163
  unpricedModel?: boolean | undefined;
@@ -119,6 +179,38 @@ export declare class AIBudget {
119
179
  }[];
120
180
  status: "blocked" | "pending" | "success" | "error";
121
181
  }[]>;
182
+ /** One request, including its stored prompt and response. */
183
+ get: (ctx: RunQueryCtx, args: {
184
+ requestId: string;
185
+ }) => Promise<{
186
+ _id: string;
187
+ _creationTime: number;
188
+ actionName?: string | undefined;
189
+ tags?: {
190
+ dimension: string;
191
+ value: string;
192
+ }[] | undefined;
193
+ estimatedNanos?: number | undefined;
194
+ estimatedTokens?: number | undefined;
195
+ unpricedModel?: boolean | undefined;
196
+ overBudget?: boolean | undefined;
197
+ settled?: boolean | undefined;
198
+ error?: string | undefined;
199
+ responseText?: string | undefined;
200
+ promptTokens?: number | undefined;
201
+ completionTokens?: number | undefined;
202
+ cachedTokens?: number | undefined;
203
+ costNanos?: number | undefined;
204
+ latencyMs?: number | undefined;
205
+ rerunOf?: string | undefined;
206
+ userId: string;
207
+ model: string;
208
+ messages: {
209
+ role: string;
210
+ content: string;
211
+ }[];
212
+ status: "blocked" | "pending" | "success" | "error";
213
+ } | null>;
122
214
  /** Ancestors up to the original, plus direct re-runs. */
123
215
  lineage: (ctx: RunQueryCtx, args: {
124
216
  requestId: string;
@@ -127,6 +219,10 @@ export declare class AIBudget {
127
219
  _id: string;
128
220
  _creationTime: number;
129
221
  actionName?: string | undefined;
222
+ tags?: {
223
+ dimension: string;
224
+ value: string;
225
+ }[] | undefined;
130
226
  estimatedNanos?: number | undefined;
131
227
  estimatedTokens?: number | undefined;
132
228
  unpricedModel?: boolean | undefined;
@@ -152,6 +248,10 @@ export declare class AIBudget {
152
248
  _id: string;
153
249
  _creationTime: number;
154
250
  actionName?: string | undefined;
251
+ tags?: {
252
+ dimension: string;
253
+ value: string;
254
+ }[] | undefined;
155
255
  estimatedNanos?: number | undefined;
156
256
  estimatedTokens?: number | undefined;
157
257
  unpricedModel?: boolean | undefined;
@@ -181,51 +281,281 @@ export declare class AIBudget {
181
281
  model?: string;
182
282
  }) => Promise<ChatResult>;
183
283
  };
184
- /** Per-user budgets and controls. */
284
+ /**
285
+ * Budgets and controls for an arbitrary attribution dimension — the
286
+ * generalization of `users`/`actions`. Give it any dimension name (team,
287
+ * project, tenant, customer, env, feature, …) and set caps per value:
288
+ *
289
+ * ai.tag("customer").setLimits(ctx, { value: "acme", monthlySpendLimitNanos });
290
+ * ai.tag("customer").history(ctx, { value: "acme", period: "day" });
291
+ *
292
+ * Attribute a call to it by passing `tags` to `chat`/`languageModel`.
293
+ */
294
+ tag(dimension: string): {
295
+ /** All buckets in this dimension. */
296
+ list: (ctx: RunQueryCtx) => Promise<{
297
+ spendTodayNanos: number;
298
+ spendThisMonthNanos: number;
299
+ _id: string;
300
+ _creationTime: number;
301
+ requestsPerMinute?: number | undefined;
302
+ maxConcurrent?: number | undefined;
303
+ dailySpendLimitNanos?: number | undefined;
304
+ monthlySpendLimitNanos?: number | undefined;
305
+ lifetimeSpendLimitNanos?: number | undefined;
306
+ dailyTokenLimit?: number | undefined;
307
+ monthlyTokenLimit?: number | undefined;
308
+ lifetimeTokenLimit?: number | undefined;
309
+ blocked?: boolean | undefined;
310
+ warnAtPct?: number | undefined;
311
+ enforcement?: "hard" | "soft" | undefined;
312
+ dailyBumpNanos?: number | undefined;
313
+ monthlyBumpNanos?: number | undefined;
314
+ lifetimeBumpNanos?: number | undefined;
315
+ bumpDayStamp?: string | undefined;
316
+ bumpMonthStamp?: string | undefined;
317
+ tokensToday?: number | undefined;
318
+ monthStamp?: string | undefined;
319
+ tokensThisMonth?: number | undefined;
320
+ reservedTodayNanos?: number | undefined;
321
+ reservedMonthNanos?: number | undefined;
322
+ reservedTotalNanos?: number | undefined;
323
+ reservedTodayTokens?: number | undefined;
324
+ reservedMonthTokens?: number | undefined;
325
+ reservedTotalTokens?: number | undefined;
326
+ pendingCount?: number | undefined;
327
+ dimension: string;
328
+ value: string;
329
+ totalSpendNanos: number;
330
+ totalRequests: number;
331
+ totalTokens: number;
332
+ dayStamp: string;
333
+ }[]>;
334
+ /** One bucket's limits + spend (null if it has none yet). */
335
+ get: (ctx: RunQueryCtx, args: {
336
+ value: string;
337
+ }) => Promise<{
338
+ spendTodayNanos: number;
339
+ spendThisMonthNanos: number;
340
+ _id: string;
341
+ _creationTime: number;
342
+ requestsPerMinute?: number | undefined;
343
+ maxConcurrent?: number | undefined;
344
+ dailySpendLimitNanos?: number | undefined;
345
+ monthlySpendLimitNanos?: number | undefined;
346
+ lifetimeSpendLimitNanos?: number | undefined;
347
+ dailyTokenLimit?: number | undefined;
348
+ monthlyTokenLimit?: number | undefined;
349
+ lifetimeTokenLimit?: number | undefined;
350
+ blocked?: boolean | undefined;
351
+ warnAtPct?: number | undefined;
352
+ enforcement?: "hard" | "soft" | undefined;
353
+ dailyBumpNanos?: number | undefined;
354
+ monthlyBumpNanos?: number | undefined;
355
+ lifetimeBumpNanos?: number | undefined;
356
+ bumpDayStamp?: string | undefined;
357
+ bumpMonthStamp?: string | undefined;
358
+ tokensToday?: number | undefined;
359
+ monthStamp?: string | undefined;
360
+ tokensThisMonth?: number | undefined;
361
+ reservedTodayNanos?: number | undefined;
362
+ reservedMonthNanos?: number | undefined;
363
+ reservedTotalNanos?: number | undefined;
364
+ reservedTodayTokens?: number | undefined;
365
+ reservedMonthTokens?: number | undefined;
366
+ reservedTotalTokens?: number | undefined;
367
+ pendingCount?: number | undefined;
368
+ dimension: string;
369
+ value: string;
370
+ totalSpendNanos: number;
371
+ totalRequests: number;
372
+ totalTokens: number;
373
+ dayStamp: string;
374
+ } | null>;
375
+ setLimits: (ctx: RunMutationCtx, args: {
376
+ value: string;
377
+ } & BucketLimits) => Promise<null>;
378
+ /** One-time "approve another $X" bump (daily/monthly reset with the window). */
379
+ bump: (ctx: RunMutationCtx, args: {
380
+ value: string;
381
+ } & BumpArgs) => Promise<null>;
382
+ /** Manually credit (negative) or debit (positive) this bucket. */
383
+ adjust: (ctx: RunMutationCtx, args: {
384
+ value: string;
385
+ } & {
386
+ deltaNanos: number;
387
+ tokens?: number;
388
+ reason?: string;
389
+ }) => Promise<null>;
390
+ /** Durable spend history for this bucket (per day or per month). */
391
+ history: (ctx: RunQueryCtx, args: {
392
+ value: string;
393
+ } & {
394
+ period?: "day" | "month";
395
+ limit?: number;
396
+ }) => Promise<{
397
+ _id: string;
398
+ _creationTime: number;
399
+ dimension: string;
400
+ value: string;
401
+ period: "day" | "month";
402
+ stamp: string;
403
+ spendNanos: number;
404
+ tokens: number;
405
+ requests: number;
406
+ }[]>;
407
+ /** Manual-adjustment audit log for this bucket. */
408
+ adjustments: (ctx: RunQueryCtx, args: {
409
+ value: string;
410
+ } & {
411
+ limit?: number;
412
+ }) => Promise<{
413
+ _id: string;
414
+ _creationTime: number;
415
+ tokens?: number | undefined;
416
+ reason?: string | undefined;
417
+ dimension: string;
418
+ value: string;
419
+ deltaNanos: number;
420
+ }[]>;
421
+ /** Delete the bucket (for "user", also its request rows). */
422
+ delete: (ctx: RunMutationCtx, args: {
423
+ value: string;
424
+ }) => Promise<{
425
+ deletedThisBatch: number;
426
+ done: boolean;
427
+ }>;
428
+ };
429
+ private dimensionApi;
430
+ /** Per-user budgets and controls — sugar over the "user" dimension. */
185
431
  get users(): {
432
+ /** All buckets in this dimension. */
186
433
  list: (ctx: RunQueryCtx) => Promise<{
187
434
  spendTodayNanos: number;
435
+ spendThisMonthNanos: number;
188
436
  _id: string;
189
437
  _creationTime: number;
190
438
  requestsPerMinute?: number | undefined;
439
+ maxConcurrent?: number | undefined;
191
440
  dailySpendLimitNanos?: number | undefined;
441
+ monthlySpendLimitNanos?: number | undefined;
192
442
  lifetimeSpendLimitNanos?: number | undefined;
193
443
  dailyTokenLimit?: number | undefined;
444
+ monthlyTokenLimit?: number | undefined;
194
445
  lifetimeTokenLimit?: number | undefined;
195
446
  blocked?: boolean | undefined;
447
+ warnAtPct?: number | undefined;
196
448
  enforcement?: "hard" | "soft" | undefined;
197
449
  dailyBumpNanos?: number | undefined;
450
+ monthlyBumpNanos?: number | undefined;
198
451
  lifetimeBumpNanos?: number | undefined;
199
452
  bumpDayStamp?: string | undefined;
453
+ bumpMonthStamp?: string | undefined;
200
454
  tokensToday?: number | undefined;
455
+ monthStamp?: string | undefined;
456
+ tokensThisMonth?: number | undefined;
201
457
  reservedTodayNanos?: number | undefined;
458
+ reservedMonthNanos?: number | undefined;
202
459
  reservedTotalNanos?: number | undefined;
203
460
  reservedTodayTokens?: number | undefined;
461
+ reservedMonthTokens?: number | undefined;
204
462
  reservedTotalTokens?: number | undefined;
205
463
  pendingCount?: number | undefined;
206
- userId: string;
464
+ dimension: string;
465
+ value: string;
207
466
  totalSpendNanos: number;
208
467
  totalRequests: number;
209
468
  totalTokens: number;
210
469
  dayStamp: string;
211
470
  }[]>;
471
+ /** One bucket's limits + spend (null if it has none yet). */
472
+ get: (ctx: RunQueryCtx, args: {
473
+ userId: string;
474
+ }) => Promise<{
475
+ spendTodayNanos: number;
476
+ spendThisMonthNanos: number;
477
+ _id: string;
478
+ _creationTime: number;
479
+ requestsPerMinute?: number | undefined;
480
+ maxConcurrent?: number | undefined;
481
+ dailySpendLimitNanos?: number | undefined;
482
+ monthlySpendLimitNanos?: number | undefined;
483
+ lifetimeSpendLimitNanos?: number | undefined;
484
+ dailyTokenLimit?: number | undefined;
485
+ monthlyTokenLimit?: number | undefined;
486
+ lifetimeTokenLimit?: number | undefined;
487
+ blocked?: boolean | undefined;
488
+ warnAtPct?: number | undefined;
489
+ enforcement?: "hard" | "soft" | undefined;
490
+ dailyBumpNanos?: number | undefined;
491
+ monthlyBumpNanos?: number | undefined;
492
+ lifetimeBumpNanos?: number | undefined;
493
+ bumpDayStamp?: string | undefined;
494
+ bumpMonthStamp?: string | undefined;
495
+ tokensToday?: number | undefined;
496
+ monthStamp?: string | undefined;
497
+ tokensThisMonth?: number | undefined;
498
+ reservedTodayNanos?: number | undefined;
499
+ reservedMonthNanos?: number | undefined;
500
+ reservedTotalNanos?: number | undefined;
501
+ reservedTodayTokens?: number | undefined;
502
+ reservedMonthTokens?: number | undefined;
503
+ reservedTotalTokens?: number | undefined;
504
+ pendingCount?: number | undefined;
505
+ dimension: string;
506
+ value: string;
507
+ totalSpendNanos: number;
508
+ totalRequests: number;
509
+ totalTokens: number;
510
+ dayStamp: string;
511
+ } | null>;
212
512
  setLimits: (ctx: RunMutationCtx, args: {
213
513
  userId: string;
214
- requestsPerMinute?: number;
215
- dailySpendLimitNanos?: number;
216
- lifetimeSpendLimitNanos?: number;
217
- dailyTokenLimit?: number;
218
- lifetimeTokenLimit?: number;
219
- enforcement?: "hard" | "soft";
220
- blocked?: boolean;
221
- }) => Promise<null>;
222
- /** One-time "approve another $X" bump (daily is today-only). */
514
+ } & BucketLimits) => Promise<null>;
515
+ /** One-time "approve another $X" bump (daily/monthly reset with the window). */
223
516
  bump: (ctx: RunMutationCtx, args: {
224
517
  userId: string;
225
- dailyNanos?: number;
226
- lifetimeNanos?: number;
518
+ } & BumpArgs) => Promise<null>;
519
+ /** Manually credit (negative) or debit (positive) this bucket. */
520
+ adjust: (ctx: RunMutationCtx, args: {
521
+ userId: string;
522
+ } & {
523
+ deltaNanos: number;
524
+ tokens?: number;
525
+ reason?: string;
227
526
  }) => Promise<null>;
228
- /** Delete a user and all their request rows. */
527
+ /** Durable spend history for this bucket (per day or per month). */
528
+ history: (ctx: RunQueryCtx, args: {
529
+ userId: string;
530
+ } & {
531
+ period?: "day" | "month";
532
+ limit?: number;
533
+ }) => Promise<{
534
+ _id: string;
535
+ _creationTime: number;
536
+ dimension: string;
537
+ value: string;
538
+ period: "day" | "month";
539
+ stamp: string;
540
+ spendNanos: number;
541
+ tokens: number;
542
+ requests: number;
543
+ }[]>;
544
+ /** Manual-adjustment audit log for this bucket. */
545
+ adjustments: (ctx: RunQueryCtx, args: {
546
+ userId: string;
547
+ } & {
548
+ limit?: number;
549
+ }) => Promise<{
550
+ _id: string;
551
+ _creationTime: number;
552
+ tokens?: number | undefined;
553
+ reason?: string | undefined;
554
+ dimension: string;
555
+ value: string;
556
+ deltaNanos: number;
557
+ }[]>;
558
+ /** Delete the bucket (for "user", also its request rows). */
229
559
  delete: (ctx: RunMutationCtx, args: {
230
560
  userId: string;
231
561
  }) => Promise<{
@@ -233,49 +563,143 @@ export declare class AIBudget {
233
563
  done: boolean;
234
564
  }>;
235
565
  };
236
- /** Per-action (per-feature) budgets. */
566
+ /** Per-action (per-feature) budgets — sugar over the "action" dimension. */
237
567
  get actions(): {
568
+ /** All buckets in this dimension. */
238
569
  list: (ctx: RunQueryCtx) => Promise<{
239
570
  spendTodayNanos: number;
571
+ spendThisMonthNanos: number;
240
572
  _id: string;
241
573
  _creationTime: number;
574
+ requestsPerMinute?: number | undefined;
575
+ maxConcurrent?: number | undefined;
242
576
  dailySpendLimitNanos?: number | undefined;
577
+ monthlySpendLimitNanos?: number | undefined;
243
578
  lifetimeSpendLimitNanos?: number | undefined;
244
579
  dailyTokenLimit?: number | undefined;
580
+ monthlyTokenLimit?: number | undefined;
245
581
  lifetimeTokenLimit?: number | undefined;
582
+ blocked?: boolean | undefined;
583
+ warnAtPct?: number | undefined;
246
584
  enforcement?: "hard" | "soft" | undefined;
247
585
  dailyBumpNanos?: number | undefined;
586
+ monthlyBumpNanos?: number | undefined;
248
587
  lifetimeBumpNanos?: number | undefined;
249
588
  bumpDayStamp?: string | undefined;
589
+ bumpMonthStamp?: string | undefined;
250
590
  tokensToday?: number | undefined;
591
+ monthStamp?: string | undefined;
592
+ tokensThisMonth?: number | undefined;
251
593
  reservedTodayNanos?: number | undefined;
594
+ reservedMonthNanos?: number | undefined;
252
595
  reservedTotalNanos?: number | undefined;
253
596
  reservedTodayTokens?: number | undefined;
597
+ reservedMonthTokens?: number | undefined;
254
598
  reservedTotalTokens?: number | undefined;
255
599
  pendingCount?: number | undefined;
256
- disabled?: boolean | undefined;
600
+ dimension: string;
601
+ value: string;
257
602
  totalSpendNanos: number;
258
603
  totalRequests: number;
259
604
  totalTokens: number;
260
605
  dayStamp: string;
261
- name: string;
262
606
  }[]>;
607
+ /** One bucket's limits + spend (null if it has none yet). */
608
+ get: (ctx: RunQueryCtx, args: {
609
+ name: string;
610
+ }) => Promise<{
611
+ spendTodayNanos: number;
612
+ spendThisMonthNanos: number;
613
+ _id: string;
614
+ _creationTime: number;
615
+ requestsPerMinute?: number | undefined;
616
+ maxConcurrent?: number | undefined;
617
+ dailySpendLimitNanos?: number | undefined;
618
+ monthlySpendLimitNanos?: number | undefined;
619
+ lifetimeSpendLimitNanos?: number | undefined;
620
+ dailyTokenLimit?: number | undefined;
621
+ monthlyTokenLimit?: number | undefined;
622
+ lifetimeTokenLimit?: number | undefined;
623
+ blocked?: boolean | undefined;
624
+ warnAtPct?: number | undefined;
625
+ enforcement?: "hard" | "soft" | undefined;
626
+ dailyBumpNanos?: number | undefined;
627
+ monthlyBumpNanos?: number | undefined;
628
+ lifetimeBumpNanos?: number | undefined;
629
+ bumpDayStamp?: string | undefined;
630
+ bumpMonthStamp?: string | undefined;
631
+ tokensToday?: number | undefined;
632
+ monthStamp?: string | undefined;
633
+ tokensThisMonth?: number | undefined;
634
+ reservedTodayNanos?: number | undefined;
635
+ reservedMonthNanos?: number | undefined;
636
+ reservedTotalNanos?: number | undefined;
637
+ reservedTodayTokens?: number | undefined;
638
+ reservedMonthTokens?: number | undefined;
639
+ reservedTotalTokens?: number | undefined;
640
+ pendingCount?: number | undefined;
641
+ dimension: string;
642
+ value: string;
643
+ totalSpendNanos: number;
644
+ totalRequests: number;
645
+ totalTokens: number;
646
+ dayStamp: string;
647
+ } | null>;
263
648
  setLimits: (ctx: RunMutationCtx, args: {
264
649
  name: string;
265
- dailySpendLimitNanos?: number;
266
- lifetimeSpendLimitNanos?: number;
267
- dailyTokenLimit?: number;
268
- lifetimeTokenLimit?: number;
269
- enforcement?: "hard" | "soft";
270
- disabled?: boolean;
271
- }) => Promise<null>;
650
+ } & BucketLimits) => Promise<null>;
651
+ /** One-time "approve another $X" bump (daily/monthly reset with the window). */
272
652
  bump: (ctx: RunMutationCtx, args: {
273
653
  name: string;
274
- dailyNanos?: number;
275
- lifetimeNanos?: number;
654
+ } & BumpArgs) => Promise<null>;
655
+ /** Manually credit (negative) or debit (positive) this bucket. */
656
+ adjust: (ctx: RunMutationCtx, args: {
657
+ name: string;
658
+ } & {
659
+ deltaNanos: number;
660
+ tokens?: number;
661
+ reason?: string;
276
662
  }) => Promise<null>;
663
+ /** Durable spend history for this bucket (per day or per month). */
664
+ history: (ctx: RunQueryCtx, args: {
665
+ name: string;
666
+ } & {
667
+ period?: "day" | "month";
668
+ limit?: number;
669
+ }) => Promise<{
670
+ _id: string;
671
+ _creationTime: number;
672
+ dimension: string;
673
+ value: string;
674
+ period: "day" | "month";
675
+ stamp: string;
676
+ spendNanos: number;
677
+ tokens: number;
678
+ requests: number;
679
+ }[]>;
680
+ /** Manual-adjustment audit log for this bucket. */
681
+ adjustments: (ctx: RunQueryCtx, args: {
682
+ name: string;
683
+ } & {
684
+ limit?: number;
685
+ }) => Promise<{
686
+ _id: string;
687
+ _creationTime: number;
688
+ tokens?: number | undefined;
689
+ reason?: string | undefined;
690
+ dimension: string;
691
+ value: string;
692
+ deltaNanos: number;
693
+ }[]>;
694
+ /** Delete the bucket (for "user", also its request rows). */
695
+ delete: (ctx: RunMutationCtx, args: {
696
+ name: string;
697
+ }) => Promise<{
698
+ deletedThisBatch: number;
699
+ done: boolean;
700
+ }>;
277
701
  };
278
- /** The deployment-wide budget and retention config. */
702
+ /** The deployment-wide budget, alerts, and retention config. */
279
703
  get global(): {
280
704
  /** Limits + spend today/total. */
281
705
  status: (ctx: RunQueryCtx) => Promise<{
@@ -284,6 +708,8 @@ export declare class AIBudget {
284
708
  enforcement: "hard" | "soft";
285
709
  spentTodayNanos: number;
286
710
  spentTotalNanos: number;
711
+ retentionMs: number | null;
712
+ defaultWarnAtPct: number | null;
287
713
  }>;
288
714
  /** A killswitch spend cap across all users/actions (enforced approximately). */
289
715
  setLimits: (ctx: RunMutationCtx, args: {
@@ -295,6 +721,10 @@ export declare class AIBudget {
295
721
  dailyNanos?: number;
296
722
  lifetimeNanos?: number;
297
723
  }) => Promise<null>;
724
+ /** Default approaching-limit alert threshold (fraction of a cap, e.g. 0.8). */
725
+ setAlertDefaults: (ctx: RunMutationCtx, args: {
726
+ warnAtPct?: number;
727
+ }) => Promise<null>;
298
728
  /** Request-row retention window in ms (default 1h; 0 disables). */
299
729
  setRetention: (ctx: RunMutationCtx, args: {
300
730
  retentionMs: number;
@@ -318,6 +748,7 @@ export declare class AIBudget {
318
748
  [x: string]: {
319
749
  input: number;
320
750
  output: number;
751
+ cached?: number | undefined;
321
752
  overridden: boolean;
322
753
  };
323
754
  }>;
@@ -325,8 +756,36 @@ export declare class AIBudget {
325
756
  model: string;
326
757
  inputNanosPerMTok: number;
327
758
  outputNanosPerMTok: number;
759
+ /** Cache-read rate; defaults to a discount off input if omitted. */
760
+ cachedNanosPerMTok?: number;
328
761
  }) => Promise<null>;
329
762
  };
763
+ /**
764
+ * Mount the built-in admin dashboard on your app's HTTP router with one call.
765
+ * Serves a self-contained HTML dashboard (buckets, requests, usage history,
766
+ * settings) plus a small JSON API, all backed by the component — no extra
767
+ * queries to write.
768
+ *
769
+ * // convex/http.ts
770
+ * import { httpRouter } from "convex/server";
771
+ * const http = httpRouter();
772
+ * ai.registerRoutes(http, { authorize: async (ctx) =>
773
+ * (await ctx.auth.getUserIdentity())?.role === "admin" });
774
+ * export default http;
775
+ *
776
+ * It then lives at `https://<deployment>.convex.site/aibudget`.
777
+ *
778
+ * SECURITY: the endpoint is public on the internet. You MUST gate it — either
779
+ * pass `authorize` (recommended: check the caller is a deployment admin) or
780
+ * set the `AI_BUDGET_DASHBOARD_TOKEN` env var (a bearer token / `?token=`).
781
+ * With neither, every route returns 401.
782
+ */
783
+ registerRoutes(http: HttpRouter, opts?: {
784
+ /** Mount path (default "/aibudget"). */
785
+ path?: string;
786
+ /** Return true to allow the request. Runs on the HTML page and every API call. */
787
+ authorize?: (ctx: any, request: Request) => boolean | Promise<boolean>;
788
+ }): void;
330
789
  }
331
790
  /** @deprecated Renamed to `AIBudget`. */
332
791
  export declare const WorryFreeAI: typeof AIBudget;