@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.
@@ -49,22 +49,32 @@ export type AIGatewayApi = AIBudgetApi;
49
49
  */
50
50
  export type Tag = { dimension: string; value: string };
51
51
 
52
- /** Fired when a request is admitted over a *soft* limit. */
53
- export type SoftLimitInfo = {
52
+ /** Common shape for budget-event callbacks. */
53
+ export type BudgetEventInfo = {
54
54
  userId: string;
55
55
  action?: string;
56
56
  tags?: Tag[];
57
- requestId: string;
58
- warnings: string[];
57
+ requestId?: string;
58
+ /** soft-cap warnings (onSoftLimit) or approaching-cap notices (onThreshold). */
59
+ messages: string[];
60
+ /** rejection code/reason (onLimitReached only). */
61
+ code?: string;
62
+ reason?: string;
59
63
  };
64
+ /** @deprecated use BudgetEventInfo */
65
+ export type SoftLimitInfo = BudgetEventInfo & { warnings: string[] };
60
66
  export type AIBudgetOptions = {
61
67
  defaultModel?: string;
62
68
  /**
63
- * Called when a soft limit is exceeded (the request is still allowed). Lets
64
- * you surface budget warnings even on the languageModel/Agent path, where
65
- * they can't be returned. Errors thrown here are swallowed.
69
+ * A *soft* limit was exceeded (request still allowed). Lets you surface budget
70
+ * warnings even on the languageModel/Agent path where they can't be returned.
71
+ * Errors thrown in any of these callbacks are swallowed.
66
72
  */
67
73
  onSoftLimit?: (info: SoftLimitInfo) => void | Promise<void>;
74
+ /** Usage crossed a bucket's warnAtPct threshold (approaching a cap). */
75
+ onThreshold?: (info: BudgetEventInfo) => void | Promise<void>;
76
+ /** A *hard* limit blocked the request (fires just before chat/model throws). */
77
+ onLimitReached?: (info: BudgetEventInfo) => void | Promise<void>;
68
78
  };
69
79
 
70
80
  type RunQueryCtx = {
@@ -123,6 +133,8 @@ export type ChatResult = {
123
133
  cachedTokens: number;
124
134
  /** Soft-limit warnings raised at admission (empty unless a soft cap was hit). */
125
135
  warnings: string[];
136
+ /** Approaching-cap notices (empty unless a warnAtPct threshold was crossed). */
137
+ notices: string[];
126
138
  };
127
139
 
128
140
  // ---------- helpers ----------
@@ -143,10 +155,12 @@ function extractUsage(usage: any): {
143
155
  return {
144
156
  promptTokens: toTokenCount(usage?.inputTokens ?? usage?.promptTokens),
145
157
  completionTokens: toTokenCount(usage?.outputTokens ?? usage?.completionTokens),
146
- // cached prompt tokens: AI SDK v5 `cachedInputTokens`, OpenAI-compat
147
- // `prompt_tokens_details.cached_tokens` / `cached_tokens`.
158
+ // cached prompt tokens. The Convex gateway reports these at
159
+ // `usage.inputTokenDetails.cacheReadTokens`; the other paths cover AI SDK v5
160
+ // (`cachedInputTokens`) and raw OpenAI-compatible shapes.
148
161
  cachedTokens: toTokenCount(
149
- usage?.cachedInputTokens ??
162
+ usage?.inputTokenDetails?.cacheReadTokens ??
163
+ usage?.cachedInputTokens ??
150
164
  usage?.promptTokensDetails?.cachedTokens ??
151
165
  usage?.prompt_tokens_details?.cached_tokens ??
152
166
  usage?.cached_tokens
@@ -154,6 +168,20 @@ function extractUsage(usage: any): {
154
168
  };
155
169
  }
156
170
 
171
+ // The gateway doesn't report a dollar cost today, but if a future response ever
172
+ // carries an unambiguous nanodollar cost we pass it straight through as
173
+ // authoritative (finishRequest prefers it over the token-based estimate). Only
174
+ // an explicitly nano-denominated field is trusted — a bare `cost` could be in
175
+ // dollars and silently mis-bill by 1e9×.
176
+ function extractGatewayCostNanos(result: any): number | undefined {
177
+ const meta = result?.providerMetadata?.convexGateway;
178
+ const candidates = [meta?.costNanos, result?.usage?.costNanos];
179
+ for (const c of candidates) {
180
+ if (typeof c === "number" && Number.isFinite(c) && c >= 0) return c;
181
+ }
182
+ return undefined;
183
+ }
184
+
157
185
  // Flatten an AI SDK prompt (roles + content parts) into simple storable messages.
158
186
  function simplifyPrompt(prompt: any): Message[] {
159
187
  if (!Array.isArray(prompt)) return [];
@@ -187,23 +215,71 @@ function extractText(result: any): string {
187
215
 
188
216
  // ---------- client ----------
189
217
 
218
+ /** Limits/controls settable on any budget bucket (user, action, or tag). */
219
+ export type BucketLimits = {
220
+ requestsPerMinute?: number;
221
+ maxConcurrent?: number;
222
+ dailySpendLimitNanos?: number;
223
+ monthlySpendLimitNanos?: number;
224
+ lifetimeSpendLimitNanos?: number;
225
+ dailyTokenLimit?: number;
226
+ monthlyTokenLimit?: number;
227
+ lifetimeTokenLimit?: number;
228
+ /** Fire an approaching-limit alert at this fraction of a cap (e.g. 0.8). */
229
+ warnAtPct?: number;
230
+ enforcement?: "hard" | "soft";
231
+ blocked?: boolean;
232
+ };
233
+ /** One-time bump amounts, added on top of a standing cap. */
234
+ export type BumpArgs = {
235
+ dailyNanos?: number;
236
+ monthlyNanos?: number;
237
+ lifetimeNanos?: number;
238
+ };
239
+
190
240
  export class AIBudget {
191
241
  public defaultModel: string;
192
242
  private onSoftLimit?: AIBudgetOptions["onSoftLimit"];
243
+ private onThreshold?: AIBudgetOptions["onThreshold"];
244
+ private onLimitReached?: AIBudgetOptions["onLimitReached"];
193
245
  constructor(
194
246
  public component: AIBudgetApi,
195
247
  options?: AIBudgetOptions
196
248
  ) {
197
249
  this.defaultModel = options?.defaultModel ?? "openai/gpt-4o-mini";
198
250
  this.onSoftLimit = options?.onSoftLimit;
251
+ this.onThreshold = options?.onThreshold;
252
+ this.onLimitReached = options?.onLimitReached;
253
+ }
254
+
255
+ // Fire the soft-limit + threshold callbacks from a startRequest result.
256
+ private async fireBudgetEvents(
257
+ base: Omit<BudgetEventInfo, "messages">,
258
+ warnings: string[],
259
+ notices: string[]
260
+ ) {
261
+ if (warnings.length > 0 && this.onSoftLimit) {
262
+ try {
263
+ await this.onSoftLimit({ ...base, messages: warnings, warnings });
264
+ } catch {
265
+ /* never let a callback error break a request */
266
+ }
267
+ }
268
+ if (notices.length > 0 && this.onThreshold) {
269
+ try {
270
+ await this.onThreshold({ ...base, messages: notices });
271
+ } catch {
272
+ /* swallow */
273
+ }
274
+ }
199
275
  }
200
276
 
201
- private async fireSoftLimit(info: SoftLimitInfo) {
202
- if (info.warnings.length === 0 || !this.onSoftLimit) return;
277
+ private async fireLimitReached(info: BudgetEventInfo) {
278
+ if (!this.onLimitReached) return;
203
279
  try {
204
- await this.onSoftLimit(info);
280
+ await this.onLimitReached(info);
205
281
  } catch {
206
- // never let a callback error break a request
282
+ /* swallow */
207
283
  }
208
284
  }
209
285
 
@@ -240,6 +316,14 @@ export class AIBudget {
240
316
  rerunOf: args.rerunOf as any,
241
317
  });
242
318
  if (!started.allowed) {
319
+ await this.fireLimitReached({
320
+ userId,
321
+ action: actionName,
322
+ tags: args.tags,
323
+ messages: [started.reason],
324
+ code: started.code,
325
+ reason: started.reason,
326
+ });
243
327
  throw new ConvexError({
244
328
  kind: "AIBudgetLimit",
245
329
  code: started.code,
@@ -248,13 +332,12 @@ export class AIBudget {
248
332
  }
249
333
  const requestId = started.requestId;
250
334
  const warnings = started.warnings;
251
- await this.fireSoftLimit({
252
- userId,
253
- action: actionName,
254
- tags: args.tags,
255
- requestId,
335
+ const notices = started.notices;
336
+ await this.fireBudgetEvents(
337
+ { userId, action: actionName, tags: args.tags, requestId },
256
338
  warnings,
257
- });
339
+ notices
340
+ );
258
341
  const start = Date.now();
259
342
  try {
260
343
  // The full chain (incl. system) is stored on the request for audit/replay,
@@ -277,10 +360,11 @@ export class AIBudget {
277
360
  requestId,
278
361
  responseText: result.text,
279
362
  ...usage,
363
+ costNanos: extractGatewayCostNanos(result),
280
364
  latencyMs: Date.now() - start,
281
365
  }
282
366
  );
283
- return { text: result.text, requestId, costNanos, warnings, ...usage };
367
+ return { text: result.text, requestId, costNanos, warnings, notices, ...usage };
284
368
  } catch (e) {
285
369
  await ctx.runMutation(this.component.lib.finishRequest, {
286
370
  requestId,
@@ -309,11 +393,13 @@ export class AIBudget {
309
393
  ): LanguageModel {
310
394
  const modelId = opts.model ?? this.defaultModel;
311
395
  const component = this.component;
312
- const fireSoftLimit = this.fireSoftLimit.bind(this);
396
+ const fireBudgetEvents = this.fireBudgetEvents.bind(this);
397
+ const fireLimitReached = this.fireLimitReached.bind(this);
313
398
 
314
399
  const begin = async (params: any) => {
315
400
  const userId = await resolveUserId(ctx, opts.userId);
316
401
  const actionName = await resolveActionName(ctx, opts.action);
402
+ const base = { userId, action: actionName, tags: opts.tags };
317
403
  const started = await ctx.runMutation(component.lib.startRequest, {
318
404
  userId,
319
405
  actionName,
@@ -322,19 +408,23 @@ export class AIBudget {
322
408
  messages: simplifyPrompt(params.prompt),
323
409
  });
324
410
  if (!started.allowed) {
411
+ await fireLimitReached({
412
+ ...base,
413
+ messages: [started.reason],
414
+ code: started.code,
415
+ reason: started.reason,
416
+ });
325
417
  throw new ConvexError({
326
418
  kind: "AIBudgetLimit",
327
419
  code: started.code,
328
420
  reason: started.reason,
329
421
  });
330
422
  }
331
- await fireSoftLimit({
332
- userId,
333
- action: actionName,
334
- tags: opts.tags,
335
- requestId: started.requestId,
336
- warnings: started.warnings,
337
- });
423
+ await fireBudgetEvents(
424
+ { ...base, requestId: started.requestId },
425
+ started.warnings,
426
+ started.notices
427
+ );
338
428
  return started.requestId;
339
429
  };
340
430
  const finish = async (
@@ -344,6 +434,8 @@ export class AIBudget {
344
434
  error?: string;
345
435
  promptTokens?: number;
346
436
  completionTokens?: number;
437
+ cachedTokens?: number;
438
+ costNanos?: number;
347
439
  latencyMs?: number;
348
440
  }
349
441
  ) => ctx.runMutation(component.lib.finishRequest, { requestId, ...fields });
@@ -359,6 +451,7 @@ export class AIBudget {
359
451
  await finish(requestId, {
360
452
  responseText: extractText(result),
361
453
  ...extractUsage(result.usage),
454
+ costNanos: extractGatewayCostNanos(result),
362
455
  latencyMs: Date.now() - start,
363
456
  });
364
457
  return result;
@@ -445,8 +538,16 @@ export class AIBudget {
445
538
  get requests() {
446
539
  const c = this.component;
447
540
  return {
448
- list: (ctx: RunQueryCtx, args: { userId?: string; limit?: number } = {}) =>
449
- ctx.runQuery(c.lib.listRequests, args),
541
+ /** Filter by userId, or by any {dimension, value} (incl. custom tags). */
542
+ list: (
543
+ ctx: RunQueryCtx,
544
+ args: {
545
+ userId?: string;
546
+ dimension?: string;
547
+ value?: string;
548
+ limit?: number;
549
+ } = {}
550
+ ) => ctx.runQuery(c.lib.listRequests, args),
450
551
  /** Ancestors up to the original, plus direct re-runs. */
451
552
  lineage: (ctx: RunQueryCtx, args: { requestId: string }) =>
452
553
  ctx.runQuery(c.lib.lineage, { requestId: args.requestId as any }),
@@ -463,130 +564,92 @@ export class AIBudget {
463
564
  * generalization of `users`/`actions`. Give it any dimension name (team,
464
565
  * project, tenant, customer, env, feature, …) and set caps per value:
465
566
  *
466
- * ai.tag("customer").setLimits(ctx, { value: "acme", dailySpendLimitNanos });
467
- * ai.tag("customer").list(ctx);
567
+ * ai.tag("customer").setLimits(ctx, { value: "acme", monthlySpendLimitNanos });
568
+ * ai.tag("customer").history(ctx, { value: "acme", period: "day" });
468
569
  *
469
570
  * Attribute a call to it by passing `tags` to `chat`/`languageModel`.
470
571
  */
471
572
  tag(dimension: string) {
472
- const c = this.component;
473
- return {
474
- /** All buckets in this dimension. */
475
- list: (ctx: RunQueryCtx) =>
476
- ctx.runQuery(c.lib.listBuckets, { dimension }),
477
- /** One bucket's limits + spend (null if it has none yet). */
478
- get: (ctx: RunQueryCtx, args: { value: string }) =>
479
- ctx.runQuery(c.lib.getBucket, { dimension, value: args.value }),
480
- setLimits: (
481
- ctx: RunMutationCtx,
482
- args: {
483
- value: string;
484
- requestsPerMinute?: number;
485
- dailySpendLimitNanos?: number;
486
- lifetimeSpendLimitNanos?: number;
487
- dailyTokenLimit?: number;
488
- lifetimeTokenLimit?: number;
489
- enforcement?: "hard" | "soft";
490
- blocked?: boolean;
491
- }
492
- ) => ctx.runMutation(c.lib.setBucketLimits, { dimension, ...args }),
493
- /** One-time "approve another $X" bump (daily is today-only). */
494
- bump: (
495
- ctx: RunMutationCtx,
496
- args: { value: string; dailyNanos?: number; lifetimeNanos?: number }
497
- ) => ctx.runMutation(c.lib.bumpBucket, { dimension, ...args }),
498
- /** Delete the bucket (for "user", also its request rows). */
499
- delete: (ctx: RunMutationCtx, args: { value: string }) =>
500
- ctx.runMutation(c.lib.deleteBucket, { dimension, value: args.value }),
501
- };
573
+ return this.dimensionApi(dimension, (a: { value: string }) => a.value);
502
574
  }
503
575
 
504
- /** Per-user budgets and controls sugar over the "user" dimension. */
505
- get users() {
576
+ // Shared implementation behind tag()/users/actions. `key` maps the namespace's
577
+ // id field (value/userId/name) to the bucket value.
578
+ private dimensionApi<A extends Record<string, any>>(
579
+ dimension: string,
580
+ key: (a: A) => string
581
+ ) {
506
582
  const c = this.component;
507
583
  return {
508
- list: (ctx: RunQueryCtx) =>
509
- ctx.runQuery(c.lib.listBuckets, { dimension: "user" }),
510
- setLimits: (
511
- ctx: RunMutationCtx,
512
- args: {
513
- userId: string;
514
- requestsPerMinute?: number;
515
- dailySpendLimitNanos?: number;
516
- lifetimeSpendLimitNanos?: number;
517
- dailyTokenLimit?: number;
518
- lifetimeTokenLimit?: number;
519
- enforcement?: "hard" | "soft";
520
- blocked?: boolean;
521
- }
522
- ) => {
523
- const { userId, ...limits } = args;
584
+ /** All buckets in this dimension. */
585
+ list: (ctx: RunQueryCtx) => ctx.runQuery(c.lib.listBuckets, { dimension }),
586
+ /** One bucket's limits + spend (null if it has none yet). */
587
+ get: (ctx: RunQueryCtx, args: A) =>
588
+ ctx.runQuery(c.lib.getBucket, { dimension, value: key(args) }),
589
+ setLimits: (ctx: RunMutationCtx, args: A & BucketLimits) => {
590
+ const { value, userId, name, ...limits } = args as any;
524
591
  return ctx.runMutation(c.lib.setBucketLimits, {
525
- dimension: "user",
526
- value: userId,
592
+ dimension,
593
+ value: key(args),
527
594
  ...limits,
528
595
  });
529
596
  },
530
- /** One-time "approve another $X" bump (daily is today-only). */
531
- bump: (
532
- ctx: RunMutationCtx,
533
- args: { userId: string; dailyNanos?: number; lifetimeNanos?: number }
534
- ) =>
597
+ /** One-time "approve another $X" bump (daily/monthly reset with the window). */
598
+ bump: (ctx: RunMutationCtx, args: A & BumpArgs) =>
535
599
  ctx.runMutation(c.lib.bumpBucket, {
536
- dimension: "user",
537
- value: args.userId,
600
+ dimension,
601
+ value: key(args),
538
602
  dailyNanos: args.dailyNanos,
603
+ monthlyNanos: args.monthlyNanos,
539
604
  lifetimeNanos: args.lifetimeNanos,
540
605
  }),
541
- /** Delete a user and all their request rows. */
542
- delete: (ctx: RunMutationCtx, args: { userId: string }) =>
543
- ctx.runMutation(c.lib.deleteBucket, {
544
- dimension: "user",
545
- value: args.userId,
606
+ /** Manually credit (negative) or debit (positive) this bucket. */
607
+ adjust: (
608
+ ctx: RunMutationCtx,
609
+ args: A & { deltaNanos: number; tokens?: number; reason?: string }
610
+ ) =>
611
+ ctx.runMutation(c.lib.adjustBucket, {
612
+ dimension,
613
+ value: key(args),
614
+ deltaNanos: args.deltaNanos,
615
+ tokens: args.tokens,
616
+ reason: args.reason,
546
617
  }),
618
+ /** Durable spend history for this bucket (per day or per month). */
619
+ history: (
620
+ ctx: RunQueryCtx,
621
+ args: A & { period?: "day" | "month"; limit?: number }
622
+ ) =>
623
+ ctx.runQuery(c.lib.usageHistory, {
624
+ dimension,
625
+ value: key(args),
626
+ period: args.period ?? "day",
627
+ limit: args.limit,
628
+ }),
629
+ /** Manual-adjustment audit log for this bucket. */
630
+ adjustments: (ctx: RunQueryCtx, args: A & { limit?: number }) =>
631
+ ctx.runQuery(c.lib.listAdjustments, {
632
+ dimension,
633
+ value: key(args),
634
+ limit: args.limit,
635
+ }),
636
+ /** Delete the bucket (for "user", also its request rows). */
637
+ delete: (ctx: RunMutationCtx, args: A) =>
638
+ ctx.runMutation(c.lib.deleteBucket, { dimension, value: key(args) }),
547
639
  };
548
640
  }
549
641
 
642
+ /** Per-user budgets and controls — sugar over the "user" dimension. */
643
+ get users() {
644
+ return this.dimensionApi<{ userId: string }>("user", (a) => a.userId);
645
+ }
646
+
550
647
  /** Per-action (per-feature) budgets — sugar over the "action" dimension. */
551
648
  get actions() {
552
- const c = this.component;
553
- return {
554
- list: (ctx: RunQueryCtx) =>
555
- ctx.runQuery(c.lib.listBuckets, { dimension: "action" }),
556
- setLimits: (
557
- ctx: RunMutationCtx,
558
- args: {
559
- name: string;
560
- dailySpendLimitNanos?: number;
561
- lifetimeSpendLimitNanos?: number;
562
- dailyTokenLimit?: number;
563
- lifetimeTokenLimit?: number;
564
- enforcement?: "hard" | "soft";
565
- /** Hard-block this action (was `disabled`). */
566
- blocked?: boolean;
567
- }
568
- ) => {
569
- const { name, ...limits } = args;
570
- return ctx.runMutation(c.lib.setBucketLimits, {
571
- dimension: "action",
572
- value: name,
573
- ...limits,
574
- });
575
- },
576
- bump: (
577
- ctx: RunMutationCtx,
578
- args: { name: string; dailyNanos?: number; lifetimeNanos?: number }
579
- ) =>
580
- ctx.runMutation(c.lib.bumpBucket, {
581
- dimension: "action",
582
- value: args.name,
583
- dailyNanos: args.dailyNanos,
584
- lifetimeNanos: args.lifetimeNanos,
585
- }),
586
- };
649
+ return this.dimensionApi<{ name: string }>("action", (a) => a.name);
587
650
  }
588
651
 
589
- /** The deployment-wide budget and retention config. */
652
+ /** The deployment-wide budget, alerts, and retention config. */
590
653
  get global() {
591
654
  const c = this.component;
592
655
  return {
@@ -605,6 +668,9 @@ export class AIBudget {
605
668
  ctx: RunMutationCtx,
606
669
  args: { dailyNanos?: number; lifetimeNanos?: number }
607
670
  ) => ctx.runMutation(c.lib.bumpGlobal, args),
671
+ /** Default approaching-limit alert threshold (fraction of a cap, e.g. 0.8). */
672
+ setAlertDefaults: (ctx: RunMutationCtx, args: { warnAtPct?: number }) =>
673
+ ctx.runMutation(c.lib.setAlertDefaults, args),
608
674
  /** Request-row retention window in ms (default 1h; 0 disables). */
609
675
  setRetention: (ctx: RunMutationCtx, args: { retentionMs: number }) =>
610
676
  ctx.runMutation(c.lib.setRetention, args),
@@ -635,6 +701,8 @@ export class AIBudget {
635
701
  model: string;
636
702
  inputNanosPerMTok: number;
637
703
  outputNanosPerMTok: number;
704
+ /** Cache-read rate; defaults to a discount off input if omitted. */
705
+ cachedNanosPerMTok?: number;
638
706
  }
639
707
  ) => ctx.runMutation(c.lib.setPrice, args),
640
708
  };
@@ -24,6 +24,19 @@ import type { FunctionReference } from "convex/server";
24
24
  export type ComponentApi<Name extends string | undefined = string | undefined> =
25
25
  {
26
26
  lib: {
27
+ adjustBucket: FunctionReference<
28
+ "mutation",
29
+ "internal",
30
+ {
31
+ deltaNanos: number;
32
+ dimension: string;
33
+ reason?: string;
34
+ tokens?: number;
35
+ value: string;
36
+ },
37
+ null,
38
+ Name
39
+ >;
27
40
  bumpBucket: FunctionReference<
28
41
  "mutation",
29
42
  "internal",
@@ -31,6 +44,7 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
31
44
  dailyNanos?: number;
32
45
  dimension: string;
33
46
  lifetimeNanos?: number;
47
+ monthlyNanos?: number;
34
48
  value: string;
35
49
  },
36
50
  null,
@@ -39,7 +53,7 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
39
53
  bumpGlobal: FunctionReference<
40
54
  "mutation",
41
55
  "internal",
42
- { dailyNanos?: number; lifetimeNanos?: number },
56
+ { dailyNanos?: number; lifetimeNanos?: number; monthlyNanos?: number },
43
57
  null,
44
58
  Name
45
59
  >;
@@ -56,6 +70,7 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
56
70
  {
57
71
  cachedTokens?: number;
58
72
  completionTokens?: number;
73
+ costNanos?: number;
59
74
  error?: string;
60
75
  latencyMs?: number;
61
76
  promptTokens?: number;
@@ -106,6 +121,13 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
106
121
  any,
107
122
  Name
108
123
  >;
124
+ listAdjustments: FunctionReference<
125
+ "query",
126
+ "internal",
127
+ { dimension: string; limit?: number; value: string },
128
+ any,
129
+ Name
130
+ >;
109
131
  listBuckets: FunctionReference<
110
132
  "query",
111
133
  "internal",
@@ -117,10 +139,17 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
117
139
  listRequests: FunctionReference<
118
140
  "query",
119
141
  "internal",
120
- { limit?: number; userId?: string },
142
+ { dimension?: string; limit?: number; userId?: string; value?: string },
121
143
  any,
122
144
  Name
123
145
  >;
146
+ setAlertDefaults: FunctionReference<
147
+ "mutation",
148
+ "internal",
149
+ { warnAtPct?: number },
150
+ null,
151
+ Name
152
+ >;
124
153
  setBucketLimits: FunctionReference<
125
154
  "mutation",
126
155
  "internal",
@@ -132,8 +161,12 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
132
161
  enforcement?: "hard" | "soft";
133
162
  lifetimeSpendLimitNanos?: number;
134
163
  lifetimeTokenLimit?: number;
164
+ maxConcurrent?: number;
165
+ monthlySpendLimitNanos?: number;
166
+ monthlyTokenLimit?: number;
135
167
  requestsPerMinute?: number;
136
168
  value: string;
169
+ warnAtPct?: number;
137
170
  },
138
171
  null,
139
172
  Name
@@ -160,6 +193,7 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
160
193
  "mutation",
161
194
  "internal",
162
195
  {
196
+ cachedNanosPerMTok?: number;
163
197
  inputNanosPerMTok: number;
164
198
  model: string;
165
199
  outputNanosPerMTok: number;
@@ -185,9 +219,26 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
185
219
  tags?: Array<{ dimension: string; value: string }>;
186
220
  userId: string;
187
221
  },
188
- | { allowed: true; requestId: string; warnings: Array<string> }
222
+ | {
223
+ allowed: true;
224
+ notices: Array<string>;
225
+ requestId: string;
226
+ warnings: Array<string>;
227
+ }
189
228
  | { allowed: false; code: string; reason: string },
190
229
  Name
191
230
  >;
231
+ usageHistory: FunctionReference<
232
+ "query",
233
+ "internal",
234
+ {
235
+ dimension: string;
236
+ limit?: number;
237
+ period: "day" | "month";
238
+ value: string;
239
+ },
240
+ any,
241
+ Name
242
+ >;
192
243
  };
193
244
  };