@convex-dev/ai-budget 0.0.2-alpha.17 → 0.0.2-alpha.19

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
@@ -251,7 +251,10 @@ await ai.meter(ctx,
251
251
  });
252
252
  ```
253
253
 
254
- ### `ai.decisions` — structured decisions (Jev)
254
+ ### `ai.decisions` — structured decisions (Jev) · **experimental**
255
+
256
+ > **Experimental** — the Decisions endpoint is alpha on the gateway; this API may
257
+ > change without a major version bump.
255
258
 
256
259
  Budget the gateway's Decisions endpoint ([Jev](https://docs.typesafe.ai)) — typed
257
260
  `choice` / `score` / `boolean` questions evaluated against a `state` — with the
@@ -280,7 +283,10 @@ defaults to `defaultEvalModel` (`"typesafe/jev-1.13"`). Requires
280
283
  `experimental_evaluate` — both imported lazily, so callers who don't use
281
284
  `decisions` are unaffected.
282
285
 
283
- ### `ai.begin` / `ai.settle` — long async jobs (video)
286
+ ### `ai.begin` / `ai.settle` — long async jobs (video) · **experimental**
287
+
288
+ > **Experimental** — video generation depends on alpha gateway features; the
289
+ > async surface (`reserveTtlMs`, `registerWebhook`) may change without a major bump.
284
290
 
285
291
  A video job is submit → wait minutes → poll/webhook → done, spanning multiple
286
292
  Convex functions, so the synchronous `meter` bracket doesn't fit. Reserve with
@@ -432,8 +438,11 @@ await ai.users.adjust(ctx, { userId, deltaNanos: -5 * 1_000_000_000, reason: "go
432
438
  await ai.users.adjustments(ctx, { userId }); // the audit log
433
439
  ```
434
440
 
435
- Negative = credit, positive = extra charge; it adjusts the live day/month/lifetime
436
- windows, the history, and an audit log.
441
+ Positive = an extra charge (adds to **gross** spend). Negative = a credit/refund,
442
+ which accrues in a **separate** credits balance and never reduces gross spend — so
443
+ gross spend and durable history stay consistent. Caps enforce on **net = gross −
444
+ credits**, so a credit gives the bucket real headroom. Both are written to the
445
+ audit log.
437
446
 
438
447
  ### Alerts
439
448
 
@@ -490,9 +499,12 @@ ai.requests.get(ctx, { requestId }) // one request
490
499
  ### Global cap
491
500
 
492
501
  ```ts
502
+ // enforcement: "approximate" (default, best-effort block) | "soft" (warn only).
503
+ // Pass null to clear a field; only the fields you pass change.
493
504
  ai.global.setLimits(ctx, { dailySpendLimitNanos?, lifetimeSpendLimitNanos?, enforcement? })
494
- ai.global.status(ctx) // { limits, spentTodayNanos, spentTotalNanos, … }
505
+ ai.global.status(ctx) // { dailySpendLimitNanos, lifetimeSpendLimitNanos, enforcement, spentTodayNanos, spentTotalNanos, … }
495
506
  ai.global.bump(ctx, { dailyNanos?, lifetimeNanos? })
507
+ ai.global.setPolicy(ctx, { allowUnpricedModels?, storeContent? }) // deployment data/pricing policy
496
508
  ```
497
509
 
498
510
  A best-effort killswitch across everything. Backed by a sharded counter for
@@ -584,19 +596,29 @@ import { AIBudget } from "@convex-dev/ai-budget";
584
596
  const ai = new AIBudget(components.aiBudget);
585
597
  const http = httpRouter();
586
598
 
587
- ai.registerRoutes(http, {
588
- // Gate it — the endpoint is public. Recommended: check the caller is an admin.
589
- authorize: async (ctx) => (await ctx.auth.getUserIdentity())?.role === "admin",
590
- });
599
+ // Recommended: token mode. Set AI_BUDGET_DASHBOARD_TOKEN in the deployment env;
600
+ // open the dashboard with ?token=<it> once and the page keeps using it.
601
+ ai.registerRoutes(http);
591
602
 
592
603
  export default http;
593
604
  ```
594
605
 
595
606
  It lives at `https://<deployment>.convex.site/aibudget` (override with `path`).
596
- **It is a public internet endpoint, so you must gate it**: pass `authorize`
597
- (return `true` to allow) or set `AI_BUDGET_DASHBOARD_TOKEN` (sent as
598
- `Authorization: Bearer …`). With neither, every route returns 401. Everything the
599
- page shows is backed by the component's own functions nothing else to wire up.
607
+ **It is a public internet endpoint, so you must gate it**, one of two ways:
608
+
609
+ - **Token (recommended, works end-to-end):** set the `AI_BUDGET_DASHBOARD_TOKEN`
610
+ env var. Open `…/aibudget?token=<token>` once; the page strips it from the URL
611
+ and sends it as a bearer on every API call. `?token=` is accepted only on the
612
+ page navigation, and the compare is constant-time.
613
+ - **`authorize(ctx, request)` callback:** must authenticate from something the
614
+ **browser sends on a top-level navigation** — a cookie or a header *you*
615
+ control — because a page load carries no `Authorization` bearer. `ctx.auth`
616
+ (deployment JWT) is `null` for the HTML page, so `authorize: (ctx) => (await
617
+ ctx.auth.getUserIdentity())?.role === "admin"` will 401 the page. Use it only
618
+ when you have your own session cookie to check.
619
+
620
+ With neither configured, every route returns 401. Everything the page shows is
621
+ backed by the component's own functions — nothing else to wire up.
600
622
 
601
623
  ---
602
624
 
@@ -641,6 +663,19 @@ more tokens or costs more than estimated, settlement records the real amount and
641
663
  the final total can exceed a hard cap by that request's estimation delta. The next
642
664
  admission sees the settled total and blocks until there is headroom again.
643
665
 
666
+ **Throughput characteristics (know these before you turn on a global cap).**
667
+ Reconciliation runs as small, independently-rescheduling phases, so folding,
668
+ expiry, and retention can't stall each other and each drains its own backlog.
669
+ Two shared-write hot spots remain, by design:
670
+ - The **global cap** reads a sharded counter *inside* admission, which contends
671
+ with every fold that writes it — so a configured global cap adds contention on
672
+ the hot path. It's a deployment-wide killswitch, not a high-throughput per-call
673
+ limit; prefer per-bucket caps for the common case.
674
+ - **Settlement writes every attributed bucket**, including the per-request
675
+ `action` bucket that every call shares. That single row is written by every
676
+ settle, so an extremely high single-action settle rate can lag totals. Spread
677
+ load across naturally-sharded dimensions (per user/customer) where you can.
678
+
644
679
  The `error.md` file documents the adversarial audits this design survived, with
645
680
  live repros.
646
681
 
@@ -732,6 +767,31 @@ metadata use their creation period and current capped buckets as a compatibility
732
767
  fallback. Exact historical hold ownership cannot be reconstructed if those caps
733
768
  changed before the upgrade. New requests always store explicit ownership.
734
769
 
770
+ ## Stability (v1)
771
+
772
+ **Stable (frozen for 1.0)** — these keep backward compatibility within the 1.x line:
773
+ `ai.chat`, `ai.meter`, `ai.begin`/`ai.settle`, `ai.languageModel`, and the admin
774
+ namespaces `ai.users` / `ai.actions` / `ai.tag` / `ai.global` / `ai.models` /
775
+ `ai.prices` / `ai.requests`, plus `ai.registerRoutes`. The stored schema is frozen;
776
+ new fields will only be added as optional.
777
+
778
+ **Experimental (may change without a major bump):** `ai.decisions` (the Jev /
779
+ Decisions endpoint) and video generation (`begin`/`settle` with `reserveTtlMs` +
780
+ `registerWebhook`). Both depend on gateway features that are still alpha.
781
+
782
+ **Semantics worth knowing:**
783
+ - **Spend caps** admit on an *estimate*, so a token-priced cap can be exceeded by
784
+ one request's estimate-vs-actual delta; pass `estimatedCostNanos` for an exact
785
+ reservation. The **global cap** is a best-effort killswitch (`enforcement:
786
+ "approximate"` | `"soft"`), not a to-the-dollar ceiling.
787
+ - **Credits** (`ai.tag(d).adjust` / negative `deltaNanos`) accrue in a separate
788
+ balance and never reduce gross spend; caps enforce on **net = gross − credits**,
789
+ so a credit grants real headroom while spend history stays consistent.
790
+ - **Deployment policy** (`ai.global.setPolicy`): `allowUnpricedModels: false`
791
+ rejects models with no configured price under hard enforcement (default charges
792
+ the conservative fallback); `storeContent: false` persists metadata but no
793
+ prompt/response content (for teams that want zero prompt retention).
794
+
735
795
  ## Development
736
796
 
737
797
  ```sh
@@ -495,6 +495,9 @@ export declare class AIBudget {
495
495
  lifetimeBumpNanos?: number | undefined;
496
496
  bumpDayStamp?: string | undefined;
497
497
  bumpMonthStamp?: string | undefined;
498
+ creditsNanos?: number | undefined;
499
+ creditsTodayNanos?: number | undefined;
500
+ creditsThisMonthNanos?: number | undefined;
498
501
  tokensToday?: number | undefined;
499
502
  monthStamp?: string | undefined;
500
503
  tokensThisMonth?: number | undefined;
@@ -536,6 +539,9 @@ export declare class AIBudget {
536
539
  lifetimeBumpNanos?: number | undefined;
537
540
  bumpDayStamp?: string | undefined;
538
541
  bumpMonthStamp?: string | undefined;
542
+ creditsNanos?: number | undefined;
543
+ creditsTodayNanos?: number | undefined;
544
+ creditsThisMonthNanos?: number | undefined;
539
545
  tokensToday?: number | undefined;
540
546
  monthStamp?: string | undefined;
541
547
  tokensThisMonth?: number | undefined;
@@ -632,6 +638,9 @@ export declare class AIBudget {
632
638
  lifetimeBumpNanos?: number | undefined;
633
639
  bumpDayStamp?: string | undefined;
634
640
  bumpMonthStamp?: string | undefined;
641
+ creditsNanos?: number | undefined;
642
+ creditsTodayNanos?: number | undefined;
643
+ creditsThisMonthNanos?: number | undefined;
635
644
  tokensToday?: number | undefined;
636
645
  monthStamp?: string | undefined;
637
646
  tokensThisMonth?: number | undefined;
@@ -673,6 +682,9 @@ export declare class AIBudget {
673
682
  lifetimeBumpNanos?: number | undefined;
674
683
  bumpDayStamp?: string | undefined;
675
684
  bumpMonthStamp?: string | undefined;
685
+ creditsNanos?: number | undefined;
686
+ creditsTodayNanos?: number | undefined;
687
+ creditsThisMonthNanos?: number | undefined;
676
688
  tokensToday?: number | undefined;
677
689
  monthStamp?: string | undefined;
678
690
  tokensThisMonth?: number | undefined;
@@ -768,6 +780,9 @@ export declare class AIBudget {
768
780
  lifetimeBumpNanos?: number | undefined;
769
781
  bumpDayStamp?: string | undefined;
770
782
  bumpMonthStamp?: string | undefined;
783
+ creditsNanos?: number | undefined;
784
+ creditsTodayNanos?: number | undefined;
785
+ creditsThisMonthNanos?: number | undefined;
771
786
  tokensToday?: number | undefined;
772
787
  monthStamp?: string | undefined;
773
788
  tokensThisMonth?: number | undefined;
@@ -809,6 +824,9 @@ export declare class AIBudget {
809
824
  lifetimeBumpNanos?: number | undefined;
810
825
  bumpDayStamp?: string | undefined;
811
826
  bumpMonthStamp?: string | undefined;
827
+ creditsNanos?: number | undefined;
828
+ creditsTodayNanos?: number | undefined;
829
+ creditsThisMonthNanos?: number | undefined;
812
830
  tokensToday?: number | undefined;
813
831
  monthStamp?: string | undefined;
814
832
  tokensThisMonth?: number | undefined;
@@ -886,17 +904,21 @@ export declare class AIBudget {
886
904
  status: (ctx: RunQueryCtx) => Promise<{
887
905
  dailySpendLimitNanos: number | null;
888
906
  lifetimeSpendLimitNanos: number | null;
889
- enforcement: "hard" | "soft";
907
+ enforcement: "soft" | "approximate";
890
908
  spentTodayNanos: number;
891
909
  spentTotalNanos: number;
892
910
  retentionMs: number | null;
893
911
  defaultWarnAtPct: number | null;
894
912
  }>;
895
- /** A killswitch spend cap across all users/actions (enforced approximately). */
913
+ /**
914
+ * A killswitch spend cap across all users/actions. Enforced
915
+ * **approximately** (bounded overshoot, no per-request reservation) — for
916
+ * an exact ceiling use a per-bucket cap. Pass `null` to clear a field.
917
+ */
896
918
  setLimits: (ctx: RunMutationCtx, args: {
897
- dailySpendLimitNanos?: number;
898
- lifetimeSpendLimitNanos?: number;
899
- enforcement?: "hard" | "soft";
919
+ dailySpendLimitNanos?: number | null;
920
+ lifetimeSpendLimitNanos?: number | null;
921
+ enforcement?: "approximate" | "soft" | null;
900
922
  }) => Promise<null>;
901
923
  bump: (ctx: RunMutationCtx, args: {
902
924
  dailyNanos?: number;
@@ -910,6 +932,17 @@ export declare class AIBudget {
910
932
  setRetention: (ctx: RunMutationCtx, args: {
911
933
  retentionMs: number;
912
934
  }) => Promise<null>;
935
+ /**
936
+ * Deployment-wide data/pricing policy (only the fields you pass change):
937
+ * - `allowUnpricedModels: false` rejects models with no configured price
938
+ * under hard enforcement (default true — charge the conservative fallback).
939
+ * - `storeContent: false` stops persisting prompt/response content on
940
+ * request rows (default true).
941
+ */
942
+ setPolicy: (ctx: RunMutationCtx, args: {
943
+ allowUnpricedModels?: boolean;
944
+ storeContent?: boolean;
945
+ }) => Promise<null>;
913
946
  };
914
947
  /** Model allow/deny policy. */
915
948
  get models(): {
@@ -39,10 +39,17 @@ function toTokenCount(x) {
39
39
  }
40
40
  function extractUsage(usage) {
41
41
  return {
42
- // Cover AI SDK camelCase (v5/v7) AND raw OpenAI-compatible snake_case, so a
43
- // `meter` caller passing a raw provider `usage` object still gets counts.
44
- promptTokens: toTokenCount(usage?.inputTokens ?? usage?.promptTokens ?? usage?.prompt_tokens),
45
- completionTokens: toTokenCount(usage?.outputTokens ?? usage?.completionTokens ?? usage?.completion_tokens),
42
+ // Cover AI SDK camelCase (v5/v7), raw OpenAI-compatible snake_case, AND raw
43
+ // Anthropic (`input_tokens`/`output_tokens`), so a `meter` caller passing any
44
+ // provider's raw `usage` object still gets counts.
45
+ promptTokens: toTokenCount(usage?.inputTokens ??
46
+ usage?.promptTokens ??
47
+ usage?.prompt_tokens ??
48
+ usage?.input_tokens),
49
+ completionTokens: toTokenCount(usage?.outputTokens ??
50
+ usage?.completionTokens ??
51
+ usage?.completion_tokens ??
52
+ usage?.output_tokens),
46
53
  // cached prompt tokens. The Convex gateway reports these at
47
54
  // `usage.inputTokenDetails.cacheReadTokens`; the other paths cover AI SDK v5
48
55
  // (`cachedInputTokens`) and raw OpenAI-compatible shapes.
@@ -50,7 +57,8 @@ function extractUsage(usage) {
50
57
  usage?.cachedInputTokens ??
51
58
  usage?.promptTokensDetails?.cachedTokens ??
52
59
  usage?.prompt_tokens_details?.cached_tokens ??
53
- usage?.cached_tokens),
60
+ usage?.cached_tokens ??
61
+ usage?.cache_read_input_tokens),
54
62
  };
55
63
  }
56
64
  // The AI Gateway reports the authoritative dollar cost of each request.
@@ -72,6 +80,10 @@ function extractGatewayCostNanos(result) {
72
80
  return undefined;
73
81
  }
74
82
  const NANOS_PER_DOLLAR = 1e9;
83
+ // Cap stored prompt/response content so one row can't approach Convex's 1 MiB
84
+ // document limit (which would fail the call) or bloat the reconciler's scans.
85
+ const MAX_STORED_CONTENT = 32 * 1024;
86
+ const capContent = (s) => s.length > MAX_STORED_CONTENT ? s.slice(0, MAX_STORED_CONTENT) + "…[truncated]" : s;
75
87
  // Flatten an AI SDK prompt (roles + content parts) into simple storable messages.
76
88
  function simplifyPrompt(prompt) {
77
89
  if (!Array.isArray(prompt))
@@ -83,13 +95,22 @@ function simplifyPrompt(prompt) {
83
95
  }
84
96
  else if (Array.isArray(m.content)) {
85
97
  content = m.content
86
- .map((part) => part?.type === "text" ? part.text : JSON.stringify(part))
98
+ .map((part) => {
99
+ if (part?.type === "text")
100
+ return part.text ?? "";
101
+ // NEVER inline a base64 image/file part — a data URL or Uint8Array here
102
+ // becomes megabytes, pushing the stored row past the 1 MiB doc limit
103
+ // (failing the call) and turning a 200 KB image into a ~50k-token
104
+ // estimate. Store a compact placeholder.
105
+ const bytes = part?.data?.length ?? part?.image?.length ?? part?.data?.byteLength;
106
+ return `[${part?.type ?? "part"}${typeof bytes === "number" ? ` ${bytes}b` : ""}]`;
107
+ })
87
108
  .join("");
88
109
  }
89
110
  else {
90
111
  content = JSON.stringify(m.content);
91
112
  }
92
- return { role: String(m.role), content };
113
+ return { role: String(m.role), content: capContent(content) };
93
114
  });
94
115
  }
95
116
  function extractText(result) {
@@ -491,6 +512,21 @@ export class AIBudget {
491
512
  async flush() {
492
513
  await settle();
493
514
  },
515
+ // A cancelled/aborted stream (client disconnect, AbortSignal, or
516
+ // breaking out of `for await`) does NOT run `flush`. Without this
517
+ // the request would sit pending until the 30-min reservation
518
+ // expiry and be recorded as $0 though the provider charged for
519
+ // what streamed. Settle here with the text so far, estimating the
520
+ // completion tokens when the provider gave no usage.
521
+ // `cancel` is a newer Streams-spec transformer hook not yet in
522
+ // the TS DOM lib types; cast the literal so it compiles (runtimes
523
+ // without it simply won't call it, and the reconciler backstops).
524
+ async cancel(reason) {
525
+ if (usage === undefined && text) {
526
+ usage = { outputTokens: Math.ceil(text.length / 4) };
527
+ }
528
+ await settle(`cancelled: ${String(reason)}`);
529
+ },
494
530
  }));
495
531
  return { ...result, stream: tapped };
496
532
  }
@@ -612,13 +648,25 @@ export class AIBudget {
612
648
  return {
613
649
  /** Limits + spend today/total. */
614
650
  status: (ctx) => ctx.runQuery(c.lib.getGlobalStatus, {}),
615
- /** A killswitch spend cap across all users/actions (enforced approximately). */
651
+ /**
652
+ * A killswitch spend cap across all users/actions. Enforced
653
+ * **approximately** (bounded overshoot, no per-request reservation) — for
654
+ * an exact ceiling use a per-bucket cap. Pass `null` to clear a field.
655
+ */
616
656
  setLimits: (ctx, args) => ctx.runMutation(c.lib.setGlobalLimits, args),
617
657
  bump: (ctx, args) => ctx.runMutation(c.lib.bumpGlobal, args),
618
658
  /** Default approaching-limit alert threshold (fraction of a cap, e.g. 0.8). */
619
659
  setAlertDefaults: (ctx, args) => ctx.runMutation(c.lib.setAlertDefaults, args),
620
660
  /** Request-row retention window in ms (default 1h; 0 disables). */
621
661
  setRetention: (ctx, args) => ctx.runMutation(c.lib.setRetention, args),
662
+ /**
663
+ * Deployment-wide data/pricing policy (only the fields you pass change):
664
+ * - `allowUnpricedModels: false` rejects models with no configured price
665
+ * under hard enforcement (default true — charge the conservative fallback).
666
+ * - `storeContent: false` stops persisting prompt/response content on
667
+ * request rows (default true).
668
+ */
669
+ setPolicy: (ctx, args) => ctx.runMutation(c.lib.setDeploymentPolicy, args),
622
670
  };
623
671
  }
624
672
  /** Model allow/deny policy. */
@@ -66,7 +66,7 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
66
66
  getGlobalStatus: FunctionReference<"query", "internal", {}, {
67
67
  dailySpendLimitNanos: number | null;
68
68
  defaultWarnAtPct: number | null;
69
- enforcement: "hard" | "soft";
69
+ enforcement: "approximate" | "soft";
70
70
  lifetimeSpendLimitNanos: number | null;
71
71
  retentionMs: number | null;
72
72
  spentTodayNanos: number;
@@ -116,10 +116,14 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
116
116
  value: string;
117
117
  warnAtPct?: number;
118
118
  }, null, Name>;
119
+ setDeploymentPolicy: FunctionReference<"mutation", "internal", {
120
+ allowUnpricedModels?: boolean;
121
+ storeContent?: boolean;
122
+ }, null, Name>;
119
123
  setGlobalLimits: FunctionReference<"mutation", "internal", {
120
- dailySpendLimitNanos?: number;
121
- enforcement?: "hard" | "soft";
122
- lifetimeSpendLimitNanos?: number;
124
+ dailySpendLimitNanos?: number | null;
125
+ enforcement?: "approximate" | "soft" | null;
126
+ lifetimeSpendLimitNanos?: number | null;
123
127
  }, null, Name>;
124
128
  setModelPolicy: FunctionReference<"mutation", "internal", {
125
129
  mode: "open" | "allowlist" | "denylist";
@@ -39,9 +39,15 @@ export declare const finishRequest: import("convex/server").RegisteredMutation<"
39
39
  export declare const foldTotals: import("convex/server").RegisteredMutation<"internal", {
40
40
  requestId: import("convex/values").GenericId<"requests">;
41
41
  }, Promise<null>>;
42
- export declare const reconcile: import("convex/server").RegisteredMutation<"internal", {}, Promise<{
42
+ export declare const reconcile: import("convex/server").RegisteredMutation<"internal", {}, Promise<null>>;
43
+ export declare const globalPhase: import("convex/server").RegisteredMutation<"internal", {}, Promise<null>>;
44
+ export declare const foldPhase: import("convex/server").RegisteredMutation<"internal", {}, Promise<{
43
45
  folded: number;
46
+ }>>;
47
+ export declare const expirePhase: import("convex/server").RegisteredMutation<"internal", {}, Promise<{
44
48
  expired: number;
49
+ }>>;
50
+ export declare const retentionPhase: import("convex/server").RegisteredMutation<"internal", {}, Promise<{
45
51
  purged: number;
46
52
  }>>;
47
53
  export declare const setRetention: import("convex/server").RegisteredMutation<"public", {
@@ -237,6 +243,9 @@ export declare const listBuckets: import("convex/server").RegisteredQuery<"publi
237
243
  lifetimeBumpNanos?: number | undefined;
238
244
  bumpDayStamp?: string | undefined;
239
245
  bumpMonthStamp?: string | undefined;
246
+ creditsNanos?: number | undefined;
247
+ creditsTodayNanos?: number | undefined;
248
+ creditsThisMonthNanos?: number | undefined;
240
249
  tokensToday?: number | undefined;
241
250
  monthStamp?: string | undefined;
242
251
  tokensThisMonth?: number | undefined;
@@ -278,6 +287,9 @@ export declare const getBucket: import("convex/server").RegisteredQuery<"public"
278
287
  lifetimeBumpNanos?: number | undefined;
279
288
  bumpDayStamp?: string | undefined;
280
289
  bumpMonthStamp?: string | undefined;
290
+ creditsNanos?: number | undefined;
291
+ creditsTodayNanos?: number | undefined;
292
+ creditsThisMonthNanos?: number | undefined;
281
293
  tokensToday?: number | undefined;
282
294
  monthStamp?: string | undefined;
283
295
  tokensThisMonth?: number | undefined;
@@ -356,6 +368,10 @@ export declare const usageHistory: import("convex/server").RegisteredQuery<"publ
356
368
  export declare const setAlertDefaults: import("convex/server").RegisteredMutation<"public", {
357
369
  warnAtPct?: number | undefined;
358
370
  }, Promise<null>>;
371
+ export declare const setDeploymentPolicy: import("convex/server").RegisteredMutation<"public", {
372
+ allowUnpricedModels?: boolean | undefined;
373
+ storeContent?: boolean | undefined;
374
+ }, Promise<null>>;
359
375
  export declare const deleteBucket: import("convex/server").RegisteredMutation<"public", {
360
376
  dimension: string;
361
377
  value: string;
@@ -370,7 +386,7 @@ export declare const getModelPolicy: import("convex/server").RegisteredQuery<"pu
370
386
  export declare const getGlobalStatus: import("convex/server").RegisteredQuery<"public", {}, Promise<{
371
387
  dailySpendLimitNanos: number | null;
372
388
  lifetimeSpendLimitNanos: number | null;
373
- enforcement: "hard" | "soft";
389
+ enforcement: "soft" | "approximate";
374
390
  spentTodayNanos: number;
375
391
  spentTotalNanos: number;
376
392
  retentionMs: number | null;
@@ -379,7 +395,7 @@ export declare const getGlobalStatus: import("convex/server").RegisteredQuery<"p
379
395
  export declare const setGlobalLimits: import("convex/server").RegisteredMutation<"public", {
380
396
  dailySpendLimitNanos?: number | null | undefined;
381
397
  lifetimeSpendLimitNanos?: number | null | undefined;
382
- enforcement?: "hard" | "soft" | null | undefined;
398
+ enforcement?: "soft" | "approximate" | null | undefined;
383
399
  }, Promise<null>>;
384
400
  export declare const bumpGlobal: import("convex/server").RegisteredMutation<"public", {
385
401
  dailyNanos?: number | undefined;