@convex-dev/ai-budget 0.0.2-alpha.3 → 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.
@@ -41,21 +41,40 @@ export type AIBudgetApi = UseApi<typeof api>;
41
41
  /** @deprecated use AIBudgetApi */
42
42
  export type AIGatewayApi = AIBudgetApi;
43
43
 
44
- /** Fired when a request is admitted over a *soft* limit. */
45
- export type SoftLimitInfo = {
44
+ /**
45
+ * One attribution tag: a (dimension, value) pair, e.g. {dimension:"customer",
46
+ * value:"acme"}. `user` and `action` are built-in dimensions (set via
47
+ * userId/action); use tags for anything else — team, project, tenant, env, ….
48
+ * Any tagged bucket can carry its own budget (see `ai.tag(dimension)`).
49
+ */
50
+ export type Tag = { dimension: string; value: string };
51
+
52
+ /** Common shape for budget-event callbacks. */
53
+ export type BudgetEventInfo = {
46
54
  userId: string;
47
55
  action?: string;
48
- requestId: string;
49
- warnings: string[];
56
+ tags?: Tag[];
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;
50
63
  };
64
+ /** @deprecated use BudgetEventInfo */
65
+ export type SoftLimitInfo = BudgetEventInfo & { warnings: string[] };
51
66
  export type AIBudgetOptions = {
52
67
  defaultModel?: string;
53
68
  /**
54
- * Called when a soft limit is exceeded (the request is still allowed). Lets
55
- * you surface budget warnings even on the languageModel/Agent path, where
56
- * 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.
57
72
  */
58
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>;
59
78
  };
60
79
 
61
80
  type RunQueryCtx = {
@@ -114,6 +133,8 @@ export type ChatResult = {
114
133
  cachedTokens: number;
115
134
  /** Soft-limit warnings raised at admission (empty unless a soft cap was hit). */
116
135
  warnings: string[];
136
+ /** Approaching-cap notices (empty unless a warnAtPct threshold was crossed). */
137
+ notices: string[];
117
138
  };
118
139
 
119
140
  // ---------- helpers ----------
@@ -134,10 +155,12 @@ function extractUsage(usage: any): {
134
155
  return {
135
156
  promptTokens: toTokenCount(usage?.inputTokens ?? usage?.promptTokens),
136
157
  completionTokens: toTokenCount(usage?.outputTokens ?? usage?.completionTokens),
137
- // cached prompt tokens: AI SDK v5 `cachedInputTokens`, OpenAI-compat
138
- // `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.
139
161
  cachedTokens: toTokenCount(
140
- usage?.cachedInputTokens ??
162
+ usage?.inputTokenDetails?.cacheReadTokens ??
163
+ usage?.cachedInputTokens ??
141
164
  usage?.promptTokensDetails?.cachedTokens ??
142
165
  usage?.prompt_tokens_details?.cached_tokens ??
143
166
  usage?.cached_tokens
@@ -145,6 +168,20 @@ function extractUsage(usage: any): {
145
168
  };
146
169
  }
147
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
+
148
185
  // Flatten an AI SDK prompt (roles + content parts) into simple storable messages.
149
186
  function simplifyPrompt(prompt: any): Message[] {
150
187
  if (!Array.isArray(prompt)) return [];
@@ -178,23 +215,71 @@ function extractText(result: any): string {
178
215
 
179
216
  // ---------- client ----------
180
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
+
181
240
  export class AIBudget {
182
241
  public defaultModel: string;
183
242
  private onSoftLimit?: AIBudgetOptions["onSoftLimit"];
243
+ private onThreshold?: AIBudgetOptions["onThreshold"];
244
+ private onLimitReached?: AIBudgetOptions["onLimitReached"];
184
245
  constructor(
185
246
  public component: AIBudgetApi,
186
247
  options?: AIBudgetOptions
187
248
  ) {
188
249
  this.defaultModel = options?.defaultModel ?? "openai/gpt-4o-mini";
189
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
+ }
190
275
  }
191
276
 
192
- private async fireSoftLimit(info: SoftLimitInfo) {
193
- if (info.warnings.length === 0 || !this.onSoftLimit) return;
277
+ private async fireLimitReached(info: BudgetEventInfo) {
278
+ if (!this.onLimitReached) return;
194
279
  try {
195
- await this.onSoftLimit(info);
280
+ await this.onLimitReached(info);
196
281
  } catch {
197
- // never let a callback error break a request
282
+ /* swallow */
198
283
  }
199
284
  }
200
285
 
@@ -213,6 +298,8 @@ export class AIBudget {
213
298
  rerunOf?: string;
214
299
  /** Attribute spend to this action name. Defaults to the calling Convex action. */
215
300
  action?: string;
301
+ /** Extra attribution dimensions to bill/limit (team, customer, env, …). */
302
+ tags?: Tag[];
216
303
  } = {}
217
304
  ): Promise<ChatResult> {
218
305
  const model = args.model ?? this.defaultModel;
@@ -223,11 +310,20 @@ export class AIBudget {
223
310
  const started = await ctx.runMutation(this.component.lib.startRequest, {
224
311
  userId,
225
312
  actionName,
313
+ tags: args.tags,
226
314
  model,
227
315
  messages,
228
316
  rerunOf: args.rerunOf as any,
229
317
  });
230
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
+ });
231
327
  throw new ConvexError({
232
328
  kind: "AIBudgetLimit",
233
329
  code: started.code,
@@ -236,7 +332,12 @@ export class AIBudget {
236
332
  }
237
333
  const requestId = started.requestId;
238
334
  const warnings = started.warnings;
239
- await this.fireSoftLimit({ userId, action: actionName, requestId, warnings });
335
+ const notices = started.notices;
336
+ await this.fireBudgetEvents(
337
+ { userId, action: actionName, tags: args.tags, requestId },
338
+ warnings,
339
+ notices
340
+ );
240
341
  const start = Date.now();
241
342
  try {
242
343
  // The full chain (incl. system) is stored on the request for audit/replay,
@@ -259,10 +360,11 @@ export class AIBudget {
259
360
  requestId,
260
361
  responseText: result.text,
261
362
  ...usage,
363
+ costNanos: extractGatewayCostNanos(result),
262
364
  latencyMs: Date.now() - start,
263
365
  }
264
366
  );
265
- return { text: result.text, requestId, costNanos, warnings, ...usage };
367
+ return { text: result.text, requestId, costNanos, warnings, notices, ...usage };
266
368
  } catch (e) {
267
369
  await ctx.runMutation(this.component.lib.finishRequest, {
268
370
  requestId,
@@ -281,34 +383,48 @@ export class AIBudget {
281
383
  */
282
384
  languageModel(
283
385
  ctx: RunMutationCtx,
284
- opts: { userId?: string; model?: string; action?: string } = {}
386
+ opts: {
387
+ userId?: string;
388
+ model?: string;
389
+ action?: string;
390
+ /** Extra attribution dimensions to bill/limit (team, customer, env, …). */
391
+ tags?: Tag[];
392
+ } = {}
285
393
  ): LanguageModel {
286
394
  const modelId = opts.model ?? this.defaultModel;
287
395
  const component = this.component;
288
- const fireSoftLimit = this.fireSoftLimit.bind(this);
396
+ const fireBudgetEvents = this.fireBudgetEvents.bind(this);
397
+ const fireLimitReached = this.fireLimitReached.bind(this);
289
398
 
290
399
  const begin = async (params: any) => {
291
400
  const userId = await resolveUserId(ctx, opts.userId);
292
401
  const actionName = await resolveActionName(ctx, opts.action);
402
+ const base = { userId, action: actionName, tags: opts.tags };
293
403
  const started = await ctx.runMutation(component.lib.startRequest, {
294
404
  userId,
295
405
  actionName,
406
+ tags: opts.tags,
296
407
  model: modelId,
297
408
  messages: simplifyPrompt(params.prompt),
298
409
  });
299
410
  if (!started.allowed) {
411
+ await fireLimitReached({
412
+ ...base,
413
+ messages: [started.reason],
414
+ code: started.code,
415
+ reason: started.reason,
416
+ });
300
417
  throw new ConvexError({
301
418
  kind: "AIBudgetLimit",
302
419
  code: started.code,
303
420
  reason: started.reason,
304
421
  });
305
422
  }
306
- await fireSoftLimit({
307
- userId,
308
- action: actionName,
309
- requestId: started.requestId,
310
- warnings: started.warnings,
311
- });
423
+ await fireBudgetEvents(
424
+ { ...base, requestId: started.requestId },
425
+ started.warnings,
426
+ started.notices
427
+ );
312
428
  return started.requestId;
313
429
  };
314
430
  const finish = async (
@@ -318,6 +434,8 @@ export class AIBudget {
318
434
  error?: string;
319
435
  promptTokens?: number;
320
436
  completionTokens?: number;
437
+ cachedTokens?: number;
438
+ costNanos?: number;
321
439
  latencyMs?: number;
322
440
  }
323
441
  ) => ctx.runMutation(component.lib.finishRequest, { requestId, ...fields });
@@ -333,6 +451,7 @@ export class AIBudget {
333
451
  await finish(requestId, {
334
452
  responseText: extractText(result),
335
453
  ...extractUsage(result.usage),
454
+ costNanos: extractGatewayCostNanos(result),
336
455
  latencyMs: Date.now() - start,
337
456
  });
338
457
  return result;
@@ -409,6 +528,7 @@ export class AIBudget {
409
528
  messages: args.messages ?? original.messages,
410
529
  rerunOf: args.requestId,
411
530
  action: original.actionName,
531
+ tags: original.tags,
412
532
  });
413
533
  }
414
534
 
@@ -418,8 +538,16 @@ export class AIBudget {
418
538
  get requests() {
419
539
  const c = this.component;
420
540
  return {
421
- list: (ctx: RunQueryCtx, args: { userId?: string; limit?: number } = {}) =>
422
- 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),
423
551
  /** Ancestors up to the original, plus direct re-runs. */
424
552
  lineage: (ctx: RunQueryCtx, args: { requestId: string }) =>
425
553
  ctx.runQuery(c.lib.lineage, { requestId: args.requestId as any }),
@@ -431,60 +559,97 @@ export class AIBudget {
431
559
  };
432
560
  }
433
561
 
434
- /** Per-user budgets and controls. */
435
- get users() {
562
+ /**
563
+ * Budgets and controls for an arbitrary attribution dimension — the
564
+ * generalization of `users`/`actions`. Give it any dimension name (team,
565
+ * project, tenant, customer, env, feature, …) and set caps per value:
566
+ *
567
+ * ai.tag("customer").setLimits(ctx, { value: "acme", monthlySpendLimitNanos });
568
+ * ai.tag("customer").history(ctx, { value: "acme", period: "day" });
569
+ *
570
+ * Attribute a call to it by passing `tags` to `chat`/`languageModel`.
571
+ */
572
+ tag(dimension: string) {
573
+ return this.dimensionApi(dimension, (a: { value: string }) => a.value);
574
+ }
575
+
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
+ ) {
436
582
  const c = this.component;
437
583
  return {
438
- list: (ctx: RunQueryCtx) => ctx.runQuery(c.lib.listUsers, {}),
439
- setLimits: (
440
- ctx: RunMutationCtx,
441
- args: {
442
- userId: string;
443
- requestsPerMinute?: number;
444
- dailySpendLimitNanos?: number;
445
- lifetimeSpendLimitNanos?: number;
446
- dailyTokenLimit?: number;
447
- lifetimeTokenLimit?: number;
448
- enforcement?: "hard" | "soft";
449
- blocked?: boolean;
450
- }
451
- ) => ctx.runMutation(c.lib.setLimits, args),
452
- /** One-time "approve another $X" bump (daily is today-only). */
453
- bump: (
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;
591
+ return ctx.runMutation(c.lib.setBucketLimits, {
592
+ dimension,
593
+ value: key(args),
594
+ ...limits,
595
+ });
596
+ },
597
+ /** One-time "approve another $X" bump (daily/monthly reset with the window). */
598
+ bump: (ctx: RunMutationCtx, args: A & BumpArgs) =>
599
+ ctx.runMutation(c.lib.bumpBucket, {
600
+ dimension,
601
+ value: key(args),
602
+ dailyNanos: args.dailyNanos,
603
+ monthlyNanos: args.monthlyNanos,
604
+ lifetimeNanos: args.lifetimeNanos,
605
+ }),
606
+ /** Manually credit (negative) or debit (positive) this bucket. */
607
+ adjust: (
454
608
  ctx: RunMutationCtx,
455
- args: { userId: string; dailyNanos?: number; lifetimeNanos?: number }
456
- ) => ctx.runMutation(c.lib.bumpUser, args),
457
- /** Delete a user and all their request rows. */
458
- delete: (ctx: RunMutationCtx, args: { userId: string }) =>
459
- ctx.runMutation(c.lib.deleteUser, args),
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,
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) }),
460
639
  };
461
640
  }
462
641
 
463
- /** Per-action (per-feature) budgets. */
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
+
647
+ /** Per-action (per-feature) budgets — sugar over the "action" dimension. */
464
648
  get actions() {
465
- const c = this.component;
466
- return {
467
- list: (ctx: RunQueryCtx) => ctx.runQuery(c.lib.listActions, {}),
468
- setLimits: (
469
- ctx: RunMutationCtx,
470
- args: {
471
- name: string;
472
- dailySpendLimitNanos?: number;
473
- lifetimeSpendLimitNanos?: number;
474
- dailyTokenLimit?: number;
475
- lifetimeTokenLimit?: number;
476
- enforcement?: "hard" | "soft";
477
- disabled?: boolean;
478
- }
479
- ) => ctx.runMutation(c.lib.setActionLimits, args),
480
- bump: (
481
- ctx: RunMutationCtx,
482
- args: { name: string; dailyNanos?: number; lifetimeNanos?: number }
483
- ) => ctx.runMutation(c.lib.bumpAction, args),
484
- };
649
+ return this.dimensionApi<{ name: string }>("action", (a) => a.name);
485
650
  }
486
651
 
487
- /** The deployment-wide budget and retention config. */
652
+ /** The deployment-wide budget, alerts, and retention config. */
488
653
  get global() {
489
654
  const c = this.component;
490
655
  return {
@@ -503,6 +668,9 @@ export class AIBudget {
503
668
  ctx: RunMutationCtx,
504
669
  args: { dailyNanos?: number; lifetimeNanos?: number }
505
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),
506
674
  /** Request-row retention window in ms (default 1h; 0 disables). */
507
675
  setRetention: (ctx: RunMutationCtx, args: { retentionMs: number }) =>
508
676
  ctx.runMutation(c.lib.setRetention, args),
@@ -533,6 +701,8 @@ export class AIBudget {
533
701
  model: string;
534
702
  inputNanosPerMTok: number;
535
703
  outputNanosPerMTok: number;
704
+ /** Cache-read rate; defaults to a discount off input if omitted. */
705
+ cachedNanosPerMTok?: number;
536
706
  }
537
707
  ) => ctx.runMutation(c.lib.setPrice, args),
538
708
  };
@@ -24,31 +24,43 @@ import type { FunctionReference } from "convex/server";
24
24
  export type ComponentApi<Name extends string | undefined = string | undefined> =
25
25
  {
26
26
  lib: {
27
- bumpAction: FunctionReference<
27
+ adjustBucket: FunctionReference<
28
28
  "mutation",
29
29
  "internal",
30
- { dailyNanos?: number; lifetimeNanos?: number; name: string },
30
+ {
31
+ deltaNanos: number;
32
+ dimension: string;
33
+ reason?: string;
34
+ tokens?: number;
35
+ value: string;
36
+ },
31
37
  null,
32
38
  Name
33
39
  >;
34
- bumpGlobal: FunctionReference<
40
+ bumpBucket: FunctionReference<
35
41
  "mutation",
36
42
  "internal",
37
- { dailyNanos?: number; lifetimeNanos?: number },
43
+ {
44
+ dailyNanos?: number;
45
+ dimension: string;
46
+ lifetimeNanos?: number;
47
+ monthlyNanos?: number;
48
+ value: string;
49
+ },
38
50
  null,
39
51
  Name
40
52
  >;
41
- bumpUser: FunctionReference<
53
+ bumpGlobal: FunctionReference<
42
54
  "mutation",
43
55
  "internal",
44
- { dailyNanos?: number; lifetimeNanos?: number; userId: string },
56
+ { dailyNanos?: number; lifetimeNanos?: number; monthlyNanos?: number },
45
57
  null,
46
58
  Name
47
59
  >;
48
- deleteUser: FunctionReference<
60
+ deleteBucket: FunctionReference<
49
61
  "mutation",
50
62
  "internal",
51
- { userId: string },
63
+ { dimension: string; value: string },
52
64
  { deletedThisBatch: number; done: boolean },
53
65
  Name
54
66
  >;
@@ -58,6 +70,7 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
58
70
  {
59
71
  cachedTokens?: number;
60
72
  completionTokens?: number;
73
+ costNanos?: number;
61
74
  error?: string;
62
75
  latencyMs?: number;
63
76
  promptTokens?: number;
@@ -67,6 +80,13 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
67
80
  { costNanos: number },
68
81
  Name
69
82
  >;
83
+ getBucket: FunctionReference<
84
+ "query",
85
+ "internal",
86
+ { dimension: string; value: string },
87
+ any,
88
+ Name
89
+ >;
70
90
  getGlobalStatus: FunctionReference<
71
91
  "query",
72
92
  "internal",
@@ -101,54 +121,63 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
101
121
  any,
102
122
  Name
103
123
  >;
104
- listActions: FunctionReference<"query", "internal", {}, any, Name>;
124
+ listAdjustments: FunctionReference<
125
+ "query",
126
+ "internal",
127
+ { dimension: string; limit?: number; value: string },
128
+ any,
129
+ Name
130
+ >;
131
+ listBuckets: FunctionReference<
132
+ "query",
133
+ "internal",
134
+ { dimension?: string },
135
+ any,
136
+ Name
137
+ >;
105
138
  listPrices: FunctionReference<"query", "internal", {}, any, Name>;
106
139
  listRequests: FunctionReference<
107
140
  "query",
108
141
  "internal",
109
- { limit?: number; userId?: string },
142
+ { dimension?: string; limit?: number; userId?: string; value?: string },
110
143
  any,
111
144
  Name
112
145
  >;
113
- listUsers: FunctionReference<"query", "internal", {}, any, Name>;
114
- setActionLimits: FunctionReference<
146
+ setAlertDefaults: FunctionReference<
115
147
  "mutation",
116
148
  "internal",
117
- {
118
- dailySpendLimitNanos?: number;
119
- dailyTokenLimit?: number;
120
- disabled?: boolean;
121
- enforcement?: "hard" | "soft";
122
- lifetimeSpendLimitNanos?: number;
123
- lifetimeTokenLimit?: number;
124
- name: string;
125
- },
149
+ { warnAtPct?: number },
126
150
  null,
127
151
  Name
128
152
  >;
129
- setGlobalLimits: FunctionReference<
153
+ setBucketLimits: FunctionReference<
130
154
  "mutation",
131
155
  "internal",
132
156
  {
157
+ blocked?: boolean;
133
158
  dailySpendLimitNanos?: number;
159
+ dailyTokenLimit?: number;
160
+ dimension: string;
134
161
  enforcement?: "hard" | "soft";
135
162
  lifetimeSpendLimitNanos?: number;
163
+ lifetimeTokenLimit?: number;
164
+ maxConcurrent?: number;
165
+ monthlySpendLimitNanos?: number;
166
+ monthlyTokenLimit?: number;
167
+ requestsPerMinute?: number;
168
+ value: string;
169
+ warnAtPct?: number;
136
170
  },
137
171
  null,
138
172
  Name
139
173
  >;
140
- setLimits: FunctionReference<
174
+ setGlobalLimits: FunctionReference<
141
175
  "mutation",
142
176
  "internal",
143
177
  {
144
- blocked?: boolean;
145
178
  dailySpendLimitNanos?: number;
146
- dailyTokenLimit?: number;
147
179
  enforcement?: "hard" | "soft";
148
180
  lifetimeSpendLimitNanos?: number;
149
- lifetimeTokenLimit?: number;
150
- requestsPerMinute?: number;
151
- userId: string;
152
181
  },
153
182
  null,
154
183
  Name
@@ -164,6 +193,7 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
164
193
  "mutation",
165
194
  "internal",
166
195
  {
196
+ cachedNanosPerMTok?: number;
167
197
  inputNanosPerMTok: number;
168
198
  model: string;
169
199
  outputNanosPerMTok: number;
@@ -186,11 +216,29 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
186
216
  messages: Array<{ content: string; role: string }>;
187
217
  model: string;
188
218
  rerunOf?: string;
219
+ tags?: Array<{ dimension: string; value: string }>;
189
220
  userId: string;
190
221
  },
191
- | { allowed: true; requestId: string; warnings: Array<string> }
222
+ | {
223
+ allowed: true;
224
+ notices: Array<string>;
225
+ requestId: string;
226
+ warnings: Array<string>;
227
+ }
192
228
  | { allowed: false; code: string; reason: string },
193
229
  Name
194
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
+ >;
195
243
  };
196
244
  };