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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,10 +25,11 @@ full audit log you can replay later.
25
25
  |---|---|
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
+ | **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. |
28
29
  | **Spend & token limits** | Per-user daily / lifetime **spend** and **token** budgets, plus a requests-per-minute rate limit and a block switch. |
29
30
  | **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×). |
30
31
  | **Hard or soft** | Each limit either **blocks** (`hard`) or **allows-with-a-warning** (`soft`). |
31
- | **Per-feature budgets** | Cap or disable a whole action (e.g. `summarize`) independently of any user. |
32
+ | **Per-feature budgets** | Cap or block a whole action (e.g. `summarize`) independently of any user. |
32
33
  | **Global killswitch** | A deployment-wide spend cap across all users and actions (sharded for throughput; enforced approximately). |
33
34
  | **One-time bumps** | "Approve another $X" at any level (user / action / global) without changing the standing cap — daily bumps are today-only, lifetime bumps permanent. |
34
35
  | **Model policy** | Allow/deny lists for models; an unknown/unpriced model **fails closed** (charged a conservative max, never $0). |
@@ -136,6 +137,7 @@ ai.chat(ctx, {
136
137
  messages?, // [{ role, content }]
137
138
  model?, // defaults to defaultModel
138
139
  action?, // attribution name; defaults to the calling Convex action
140
+ tags?, // extra dimensions: [{ dimension: "customer", value: "acme" }, …]
139
141
  }): Promise<{ text, requestId, costNanos, promptTokens, completionTokens, warnings }>
140
142
  ```
141
143
 
@@ -208,10 +210,42 @@ ai.actions.setLimits(ctx, {
208
210
  dailyTokenLimit?,
209
211
  lifetimeTokenLimit?,
210
212
  enforcement?, // "hard" | "soft"
211
- disabled?, // kill switch for the whole feature
213
+ blocked?, // kill switch for the whole feature
212
214
  })
213
215
  ```
214
216
 
217
+ ### Tagged budgets (custom dimensions)
218
+
219
+ `user` and `action` are the two built-in *dimensions*. To classify or budget
220
+ along any other axis — team, project, tenant, customer, environment, feature —
221
+ attach `tags` to a call and cap a value with `ai.tag(dimension)`:
222
+
223
+ ```ts
224
+ // Bill this call to a user, an action (implicit), AND a customer + env.
225
+ await ai.chat(ctx, {
226
+ prompt,
227
+ tags: [
228
+ { dimension: "customer", value: "acme" },
229
+ { dimension: "env", value: "prod" },
230
+ ],
231
+ });
232
+
233
+ // Cap the customer "acme" to $50/day — independent of any per-user cap.
234
+ await ai.tag("customer").setLimits(ctx, {
235
+ value: "acme",
236
+ dailySpendLimitNanos: 50 * 1_000_000_000,
237
+ });
238
+ await ai.tag("customer").bump(ctx, { value: "acme", dailyNanos: 10 * 1_000_000_000 });
239
+ await ai.tag("customer").list(ctx); // every customer's spend & caps
240
+ await ai.tag("customer").get(ctx, { value: "acme" }); // one bucket
241
+ ```
242
+
243
+ A single request is admitted only if it fits **every** bucket it touches (user,
244
+ action, and each tag) — the same exact reserve-then-settle check runs per bucket.
245
+ Uncapped buckets never serialize, so adding tags you don't cap is free at
246
+ admission; their running totals still accrue for reporting. `ai.users.*` and
247
+ `ai.actions.*` are simply sugar over `ai.tag("user")` / `ai.tag("action")`.
248
+
215
249
  ### Global (deployment-wide) budget
216
250
 
217
251
  ```ts
@@ -279,6 +313,7 @@ flagged `unpricedModel: true` so you know to add a real price.
279
313
  ```ts
280
314
  ai.users.list(ctx) // per-user spend today / total / tokens / limits
281
315
  ai.actions.list(ctx) // per-action spend & totals
316
+ ai.tag("customer").list(ctx) // spend & caps for any custom dimension
282
317
  ai.requests.list(ctx, { userId?, limit? }) // the audit log (blocked attempts included)
283
318
  ```
284
319
 
@@ -305,15 +340,18 @@ same pre-spend total and all pass, so spend blows past the cap (measured at
305
340
  **exactly-once** (a terminal request is never re-folded), so a slow request
306
341
  that the reconciler already swept can't double-count when it finally returns.
307
342
 
308
- Reservations are only taken on entities that actually have a cap, so uncapped
309
- traffic never serializes. The `error.md` file documents the adversarial audits
310
- this design survived, with live repros.
311
-
312
- **One guarantee, all scopes.** Per-user, per-action, and global caps run through
313
- the *same* admission check — a request is admitted only when
314
- `committed + reserved + estimate cap` (bumps included). The only thing that
315
- differs is the holder: per-user and per-action reserve on a single document, an
316
- exact atomic check-and-reserve; the **global** killswitch is backed by a sharded
343
+ Reservations are only taken on buckets that actually have a cap, so uncapped
344
+ traffic never serializes this is what makes arbitrary `tags` cheap: a request
345
+ reserves on one row per *capped* dimension it carries, and nothing else. The
346
+ `error.md` file documents the adversarial audits this design survived, with live
347
+ repros.
348
+
349
+ **One guarantee, all scopes.** Every per-bucket cap user, action, or any
350
+ custom tag dimension and the global cap run through the *same* admission check:
351
+ a request is admitted only when `committed + reserved + estimate ≤ cap` (bumps
352
+ included) for **every** bucket it touches. The only thing that differs is the
353
+ holder: each per-bucket cap reserves on a single document, an exact atomic
354
+ check-and-reserve; the **global** killswitch is backed by a sharded
317
355
  counter for throughput, so its committed total is read as an eventually-consistent
318
356
  sum with no cross-request reservation. That makes the global cap **approximate** —
319
357
  it can overshoot by a bounded amount under a burst — the deliberate
@@ -11,10 +11,21 @@ type UseApi<API> = Expand<{
11
11
  export type AIBudgetApi = UseApi<typeof api>;
12
12
  /** @deprecated use AIBudgetApi */
13
13
  export type AIGatewayApi = AIBudgetApi;
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
+ };
14
24
  /** Fired when a request is admitted over a *soft* limit. */
15
25
  export type SoftLimitInfo = {
16
26
  userId: string;
17
27
  action?: string;
28
+ tags?: Tag[];
18
29
  requestId: string;
19
30
  warnings: string[];
20
31
  };
@@ -76,6 +87,8 @@ export declare class AIBudget {
76
87
  rerunOf?: string;
77
88
  /** Attribute spend to this action name. Defaults to the calling Convex action. */
78
89
  action?: string;
90
+ /** Extra attribution dimensions to bill/limit (team, customer, env, …). */
91
+ tags?: Tag[];
79
92
  }): Promise<ChatResult>;
80
93
  /**
81
94
  * An AI SDK LanguageModel that enforces limits and records usage/cost for
@@ -87,6 +100,8 @@ export declare class AIBudget {
87
100
  userId?: string;
88
101
  model?: string;
89
102
  action?: string;
103
+ /** Extra attribution dimensions to bill/limit (team, customer, env, …). */
104
+ tags?: Tag[];
90
105
  }): LanguageModel;
91
106
  private rerunImpl;
92
107
  /** The request audit log, replay, and re-run lineage. */
@@ -98,6 +113,10 @@ export declare class AIBudget {
98
113
  _id: string;
99
114
  _creationTime: number;
100
115
  actionName?: string | undefined;
116
+ tags?: {
117
+ dimension: string;
118
+ value: string;
119
+ }[] | undefined;
101
120
  estimatedNanos?: number | undefined;
102
121
  estimatedTokens?: number | undefined;
103
122
  unpricedModel?: boolean | undefined;
@@ -127,6 +146,10 @@ export declare class AIBudget {
127
146
  _id: string;
128
147
  _creationTime: number;
129
148
  actionName?: string | undefined;
149
+ tags?: {
150
+ dimension: string;
151
+ value: string;
152
+ }[] | undefined;
130
153
  estimatedNanos?: number | undefined;
131
154
  estimatedTokens?: number | undefined;
132
155
  unpricedModel?: boolean | undefined;
@@ -152,6 +175,10 @@ export declare class AIBudget {
152
175
  _id: string;
153
176
  _creationTime: number;
154
177
  actionName?: string | undefined;
178
+ tags?: {
179
+ dimension: string;
180
+ value: string;
181
+ }[] | undefined;
155
182
  estimatedNanos?: number | undefined;
156
183
  estimatedTokens?: number | undefined;
157
184
  unpricedModel?: boolean | undefined;
@@ -181,7 +208,100 @@ export declare class AIBudget {
181
208
  model?: string;
182
209
  }) => Promise<ChatResult>;
183
210
  };
184
- /** Per-user budgets and controls. */
211
+ /**
212
+ * Budgets and controls for an arbitrary attribution dimension — the
213
+ * generalization of `users`/`actions`. Give it any dimension name (team,
214
+ * project, tenant, customer, env, feature, …) and set caps per value:
215
+ *
216
+ * ai.tag("customer").setLimits(ctx, { value: "acme", dailySpendLimitNanos });
217
+ * ai.tag("customer").list(ctx);
218
+ *
219
+ * Attribute a call to it by passing `tags` to `chat`/`languageModel`.
220
+ */
221
+ tag(dimension: string): {
222
+ /** All buckets in this dimension. */
223
+ list: (ctx: RunQueryCtx) => Promise<{
224
+ spendTodayNanos: number;
225
+ _id: string;
226
+ _creationTime: number;
227
+ requestsPerMinute?: number | undefined;
228
+ dailySpendLimitNanos?: number | undefined;
229
+ lifetimeSpendLimitNanos?: number | undefined;
230
+ dailyTokenLimit?: number | undefined;
231
+ lifetimeTokenLimit?: number | undefined;
232
+ blocked?: boolean | undefined;
233
+ enforcement?: "hard" | "soft" | undefined;
234
+ dailyBumpNanos?: number | undefined;
235
+ lifetimeBumpNanos?: number | undefined;
236
+ bumpDayStamp?: string | undefined;
237
+ tokensToday?: number | undefined;
238
+ reservedTodayNanos?: number | undefined;
239
+ reservedTotalNanos?: number | undefined;
240
+ reservedTodayTokens?: number | undefined;
241
+ reservedTotalTokens?: number | undefined;
242
+ pendingCount?: number | undefined;
243
+ dimension: string;
244
+ value: string;
245
+ totalSpendNanos: number;
246
+ totalRequests: number;
247
+ totalTokens: number;
248
+ dayStamp: string;
249
+ }[]>;
250
+ /** One bucket's limits + spend (null if it has none yet). */
251
+ get: (ctx: RunQueryCtx, args: {
252
+ value: string;
253
+ }) => Promise<{
254
+ spendTodayNanos: number;
255
+ _id: string;
256
+ _creationTime: number;
257
+ requestsPerMinute?: number | undefined;
258
+ dailySpendLimitNanos?: number | undefined;
259
+ lifetimeSpendLimitNanos?: number | undefined;
260
+ dailyTokenLimit?: number | undefined;
261
+ lifetimeTokenLimit?: number | undefined;
262
+ blocked?: boolean | undefined;
263
+ enforcement?: "hard" | "soft" | undefined;
264
+ dailyBumpNanos?: number | undefined;
265
+ lifetimeBumpNanos?: number | undefined;
266
+ bumpDayStamp?: string | undefined;
267
+ tokensToday?: number | undefined;
268
+ reservedTodayNanos?: number | undefined;
269
+ reservedTotalNanos?: number | undefined;
270
+ reservedTodayTokens?: number | undefined;
271
+ reservedTotalTokens?: number | undefined;
272
+ pendingCount?: number | undefined;
273
+ dimension: string;
274
+ value: string;
275
+ totalSpendNanos: number;
276
+ totalRequests: number;
277
+ totalTokens: number;
278
+ dayStamp: string;
279
+ } | null>;
280
+ setLimits: (ctx: RunMutationCtx, args: {
281
+ value: string;
282
+ requestsPerMinute?: number;
283
+ dailySpendLimitNanos?: number;
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). */
291
+ bump: (ctx: RunMutationCtx, args: {
292
+ value: string;
293
+ dailyNanos?: number;
294
+ lifetimeNanos?: number;
295
+ }) => Promise<null>;
296
+ /** Delete the bucket (for "user", also its request rows). */
297
+ delete: (ctx: RunMutationCtx, args: {
298
+ value: string;
299
+ }) => Promise<{
300
+ deletedThisBatch: number;
301
+ done: boolean;
302
+ }>;
303
+ };
304
+ /** Per-user budgets and controls — sugar over the "user" dimension. */
185
305
  get users(): {
186
306
  list: (ctx: RunQueryCtx) => Promise<{
187
307
  spendTodayNanos: number;
@@ -203,7 +323,8 @@ export declare class AIBudget {
203
323
  reservedTodayTokens?: number | undefined;
204
324
  reservedTotalTokens?: number | undefined;
205
325
  pendingCount?: number | undefined;
206
- userId: string;
326
+ dimension: string;
327
+ value: string;
207
328
  totalSpendNanos: number;
208
329
  totalRequests: number;
209
330
  totalTokens: number;
@@ -233,16 +354,18 @@ export declare class AIBudget {
233
354
  done: boolean;
234
355
  }>;
235
356
  };
236
- /** Per-action (per-feature) budgets. */
357
+ /** Per-action (per-feature) budgets — sugar over the "action" dimension. */
237
358
  get actions(): {
238
359
  list: (ctx: RunQueryCtx) => Promise<{
239
360
  spendTodayNanos: number;
240
361
  _id: string;
241
362
  _creationTime: number;
363
+ requestsPerMinute?: number | undefined;
242
364
  dailySpendLimitNanos?: number | undefined;
243
365
  lifetimeSpendLimitNanos?: number | undefined;
244
366
  dailyTokenLimit?: number | undefined;
245
367
  lifetimeTokenLimit?: number | undefined;
368
+ blocked?: boolean | undefined;
246
369
  enforcement?: "hard" | "soft" | undefined;
247
370
  dailyBumpNanos?: number | undefined;
248
371
  lifetimeBumpNanos?: number | undefined;
@@ -253,12 +376,12 @@ export declare class AIBudget {
253
376
  reservedTodayTokens?: number | undefined;
254
377
  reservedTotalTokens?: number | undefined;
255
378
  pendingCount?: number | undefined;
256
- disabled?: boolean | undefined;
379
+ dimension: string;
380
+ value: string;
257
381
  totalSpendNanos: number;
258
382
  totalRequests: number;
259
383
  totalTokens: number;
260
384
  dayStamp: string;
261
- name: string;
262
385
  }[]>;
263
386
  setLimits: (ctx: RunMutationCtx, args: {
264
387
  name: string;
@@ -267,7 +390,8 @@ export declare class AIBudget {
267
390
  dailyTokenLimit?: number;
268
391
  lifetimeTokenLimit?: number;
269
392
  enforcement?: "hard" | "soft";
270
- disabled?: boolean;
393
+ /** Hard-block this action (was `disabled`). */
394
+ blocked?: boolean;
271
395
  }) => Promise<null>;
272
396
  bump: (ctx: RunMutationCtx, args: {
273
397
  name: string;
@@ -110,6 +110,7 @@ export class AIBudget {
110
110
  const started = await ctx.runMutation(this.component.lib.startRequest, {
111
111
  userId,
112
112
  actionName,
113
+ tags: args.tags,
113
114
  model,
114
115
  messages,
115
116
  rerunOf: args.rerunOf,
@@ -123,7 +124,13 @@ export class AIBudget {
123
124
  }
124
125
  const requestId = started.requestId;
125
126
  const warnings = started.warnings;
126
- await this.fireSoftLimit({ userId, action: actionName, requestId, warnings });
127
+ await this.fireSoftLimit({
128
+ userId,
129
+ action: actionName,
130
+ tags: args.tags,
131
+ requestId,
132
+ warnings,
133
+ });
127
134
  const start = Date.now();
128
135
  try {
129
136
  // The full chain (incl. system) is stored on the request for audit/replay,
@@ -172,6 +179,7 @@ export class AIBudget {
172
179
  const started = await ctx.runMutation(component.lib.startRequest, {
173
180
  userId,
174
181
  actionName,
182
+ tags: opts.tags,
175
183
  model: modelId,
176
184
  messages: simplifyPrompt(params.prompt),
177
185
  });
@@ -185,6 +193,7 @@ export class AIBudget {
185
193
  await fireSoftLimit({
186
194
  userId,
187
195
  action: actionName,
196
+ tags: opts.tags,
188
197
  requestId: started.requestId,
189
198
  warnings: started.warnings,
190
199
  });
@@ -278,6 +287,7 @@ export class AIBudget {
278
287
  messages: args.messages ?? original.messages,
279
288
  rerunOf: args.requestId,
280
289
  action: original.actionName,
290
+ tags: original.tags,
281
291
  });
282
292
  }
283
293
  // ---------- namespaced admin API ----------
@@ -292,25 +302,76 @@ export class AIBudget {
292
302
  rerun: (ctx, args) => this.rerunImpl(ctx, args),
293
303
  };
294
304
  }
295
- /** Per-user budgets and controls. */
305
+ /**
306
+ * Budgets and controls for an arbitrary attribution dimension — the
307
+ * generalization of `users`/`actions`. Give it any dimension name (team,
308
+ * project, tenant, customer, env, feature, …) and set caps per value:
309
+ *
310
+ * ai.tag("customer").setLimits(ctx, { value: "acme", dailySpendLimitNanos });
311
+ * ai.tag("customer").list(ctx);
312
+ *
313
+ * Attribute a call to it by passing `tags` to `chat`/`languageModel`.
314
+ */
315
+ tag(dimension) {
316
+ const c = this.component;
317
+ return {
318
+ /** All buckets in this dimension. */
319
+ list: (ctx) => ctx.runQuery(c.lib.listBuckets, { dimension }),
320
+ /** One bucket's limits + spend (null if it has none yet). */
321
+ get: (ctx, args) => ctx.runQuery(c.lib.getBucket, { dimension, value: args.value }),
322
+ setLimits: (ctx, args) => ctx.runMutation(c.lib.setBucketLimits, { dimension, ...args }),
323
+ /** One-time "approve another $X" bump (daily is today-only). */
324
+ bump: (ctx, args) => ctx.runMutation(c.lib.bumpBucket, { dimension, ...args }),
325
+ /** Delete the bucket (for "user", also its request rows). */
326
+ delete: (ctx, args) => ctx.runMutation(c.lib.deleteBucket, { dimension, value: args.value }),
327
+ };
328
+ }
329
+ /** Per-user budgets and controls — sugar over the "user" dimension. */
296
330
  get users() {
297
331
  const c = this.component;
298
332
  return {
299
- list: (ctx) => ctx.runQuery(c.lib.listUsers, {}),
300
- setLimits: (ctx, args) => ctx.runMutation(c.lib.setLimits, args),
333
+ list: (ctx) => ctx.runQuery(c.lib.listBuckets, { dimension: "user" }),
334
+ setLimits: (ctx, args) => {
335
+ const { userId, ...limits } = args;
336
+ return ctx.runMutation(c.lib.setBucketLimits, {
337
+ dimension: "user",
338
+ value: userId,
339
+ ...limits,
340
+ });
341
+ },
301
342
  /** One-time "approve another $X" bump (daily is today-only). */
302
- bump: (ctx, args) => ctx.runMutation(c.lib.bumpUser, args),
343
+ bump: (ctx, args) => ctx.runMutation(c.lib.bumpBucket, {
344
+ dimension: "user",
345
+ value: args.userId,
346
+ dailyNanos: args.dailyNanos,
347
+ lifetimeNanos: args.lifetimeNanos,
348
+ }),
303
349
  /** Delete a user and all their request rows. */
304
- delete: (ctx, args) => ctx.runMutation(c.lib.deleteUser, args),
350
+ delete: (ctx, args) => ctx.runMutation(c.lib.deleteBucket, {
351
+ dimension: "user",
352
+ value: args.userId,
353
+ }),
305
354
  };
306
355
  }
307
- /** Per-action (per-feature) budgets. */
356
+ /** Per-action (per-feature) budgets — sugar over the "action" dimension. */
308
357
  get actions() {
309
358
  const c = this.component;
310
359
  return {
311
- list: (ctx) => ctx.runQuery(c.lib.listActions, {}),
312
- setLimits: (ctx, args) => ctx.runMutation(c.lib.setActionLimits, args),
313
- bump: (ctx, args) => ctx.runMutation(c.lib.bumpAction, args),
360
+ list: (ctx) => ctx.runQuery(c.lib.listBuckets, { dimension: "action" }),
361
+ setLimits: (ctx, args) => {
362
+ const { name, ...limits } = args;
363
+ return ctx.runMutation(c.lib.setBucketLimits, {
364
+ dimension: "action",
365
+ value: name,
366
+ ...limits,
367
+ });
368
+ },
369
+ bump: (ctx, args) => ctx.runMutation(c.lib.bumpBucket, {
370
+ dimension: "action",
371
+ value: args.name,
372
+ dailyNanos: args.dailyNanos,
373
+ lifetimeNanos: args.lifetimeNanos,
374
+ }),
314
375
  };
315
376
  }
316
377
  /** The deployment-wide budget and retention config. */
@@ -20,22 +20,19 @@ import type { FunctionReference } from "convex/server";
20
20
  */
21
21
  export type ComponentApi<Name extends string | undefined = string | undefined> = {
22
22
  lib: {
23
- bumpAction: FunctionReference<"mutation", "internal", {
23
+ bumpBucket: FunctionReference<"mutation", "internal", {
24
24
  dailyNanos?: number;
25
+ dimension: string;
25
26
  lifetimeNanos?: number;
26
- name: string;
27
+ value: string;
27
28
  }, null, Name>;
28
29
  bumpGlobal: FunctionReference<"mutation", "internal", {
29
30
  dailyNanos?: number;
30
31
  lifetimeNanos?: number;
31
32
  }, null, Name>;
32
- bumpUser: FunctionReference<"mutation", "internal", {
33
- dailyNanos?: number;
34
- lifetimeNanos?: number;
35
- userId: string;
36
- }, null, Name>;
37
- deleteUser: FunctionReference<"mutation", "internal", {
38
- userId: string;
33
+ deleteBucket: FunctionReference<"mutation", "internal", {
34
+ dimension: string;
35
+ value: string;
39
36
  }, {
40
37
  deletedThisBatch: number;
41
38
  done: boolean;
@@ -51,6 +48,10 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
51
48
  }, {
52
49
  costNanos: number;
53
50
  }, Name>;
51
+ getBucket: FunctionReference<"query", "internal", {
52
+ dimension: string;
53
+ value: string;
54
+ }, any, Name>;
54
55
  getGlobalStatus: FunctionReference<"query", "internal", {}, {
55
56
  dailySpendLimitNanos: number | null;
56
57
  enforcement: "hard" | "soft";
@@ -68,37 +69,30 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
68
69
  lineage: FunctionReference<"query", "internal", {
69
70
  requestId: string;
70
71
  }, any, Name>;
71
- listActions: FunctionReference<"query", "internal", {}, any, Name>;
72
+ listBuckets: FunctionReference<"query", "internal", {
73
+ dimension?: string;
74
+ }, any, Name>;
72
75
  listPrices: FunctionReference<"query", "internal", {}, any, Name>;
73
76
  listRequests: FunctionReference<"query", "internal", {
74
77
  limit?: number;
75
78
  userId?: string;
76
79
  }, any, Name>;
77
- listUsers: FunctionReference<"query", "internal", {}, any, Name>;
78
- setActionLimits: FunctionReference<"mutation", "internal", {
80
+ setBucketLimits: FunctionReference<"mutation", "internal", {
81
+ blocked?: boolean;
79
82
  dailySpendLimitNanos?: number;
80
83
  dailyTokenLimit?: number;
81
- disabled?: boolean;
84
+ dimension: string;
82
85
  enforcement?: "hard" | "soft";
83
86
  lifetimeSpendLimitNanos?: number;
84
87
  lifetimeTokenLimit?: number;
85
- name: string;
88
+ requestsPerMinute?: number;
89
+ value: string;
86
90
  }, null, Name>;
87
91
  setGlobalLimits: FunctionReference<"mutation", "internal", {
88
92
  dailySpendLimitNanos?: number;
89
93
  enforcement?: "hard" | "soft";
90
94
  lifetimeSpendLimitNanos?: number;
91
95
  }, null, Name>;
92
- setLimits: FunctionReference<"mutation", "internal", {
93
- blocked?: boolean;
94
- dailySpendLimitNanos?: number;
95
- dailyTokenLimit?: number;
96
- enforcement?: "hard" | "soft";
97
- lifetimeSpendLimitNanos?: number;
98
- lifetimeTokenLimit?: number;
99
- requestsPerMinute?: number;
100
- userId: string;
101
- }, null, Name>;
102
96
  setModelPolicy: FunctionReference<"mutation", "internal", {
103
97
  mode: "open" | "allowlist" | "denylist";
104
98
  models: Array<string>;
@@ -119,6 +113,10 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
119
113
  }>;
120
114
  model: string;
121
115
  rerunOf?: string;
116
+ tags?: Array<{
117
+ dimension: string;
118
+ value: string;
119
+ }>;
122
120
  userId: string;
123
121
  }, {
124
122
  allowed: true;