@convex-dev/ai-budget 0.0.2-alpha.14 → 0.0.2-alpha.16
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 +74 -9
- package/dist/client/index.d.ts +86 -0
- package/dist/client/index.js +69 -1
- package/dist/component/_generated/api.d.ts +1 -0
- package/dist/component/convex.config.js +2 -0
- package/dist/component/lib.d.ts +32 -0
- package/dist/component/lib.js +166 -122
- package/dist/component/schema.d.ts +53 -1
- package/dist/component/schema.js +31 -1
- package/package.json +3 -2
- package/src/client/index.ts +114 -1
- package/src/client/webhook.test.ts +32 -0
- package/src/component/_generated/api.ts +1 -0
- package/src/component/convex.config.ts +3 -0
- package/src/component/lib.test.ts +246 -29
- package/src/component/lib.ts +177 -135
- package/src/component/schema.ts +31 -1
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ full audit log you can replay later.
|
|
|
23
23
|
- [Setup](#setup)
|
|
24
24
|
- [Quickstart](#quickstart)
|
|
25
25
|
- [Concepts](#concepts) — dimensions, nanodollars, reserve→settle
|
|
26
|
-
- [Generating text](#generating-text) — `chat`, `languageModel`, replay
|
|
26
|
+
- [Generating text](#generating-text) — `chat`, `languageModel`, `meter`, `decisions`, replay
|
|
27
27
|
- [Budgets & limits](#budgets--limits) — set caps, bumps, credits, alerts
|
|
28
28
|
- [Monitoring](#monitoring) — totals, spend history, the request log
|
|
29
29
|
- [Deployment-wide controls](#deployment-wide-controls) — global cap, model policy, pricing, retention
|
|
@@ -251,6 +251,35 @@ await ai.meter(ctx,
|
|
|
251
251
|
});
|
|
252
252
|
```
|
|
253
253
|
|
|
254
|
+
### `ai.decisions` — structured decisions (Jev)
|
|
255
|
+
|
|
256
|
+
Budget the gateway's Decisions endpoint ([Jev](https://docs.typesafe.ai)) — typed
|
|
257
|
+
`choice` / `score` / `boolean` questions evaluated against a `state` — with the
|
|
258
|
+
same limits, audit log, cost tracking, and tags as `ai.chat`:
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
const { answers, costNanos } = await ai.decisions(ctx, {
|
|
262
|
+
state: { ticket: "Customer cannot sign in" },
|
|
263
|
+
questions: {
|
|
264
|
+
priority: {
|
|
265
|
+
type: "choice",
|
|
266
|
+
instructions: "Choose the response priority",
|
|
267
|
+
criteria: { urgent: "Respond now", normal: "Respond today" },
|
|
268
|
+
},
|
|
269
|
+
needsReview: { type: "boolean", instructions: "Does a human need to review this?" },
|
|
270
|
+
},
|
|
271
|
+
tags: [{ dimension: "team", value: "support" }],
|
|
272
|
+
});
|
|
273
|
+
answers.priority.choice; // "urgent" | "normal"
|
|
274
|
+
answers.needsReview.probability;
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Sugar over `ai.meter`, so cost is the gateway's authoritative amount. Model
|
|
278
|
+
defaults to `defaultEvalModel` (`"typesafe/jev-1.13"`). Requires
|
|
279
|
+
`@convex-dev/ai-sdk-provider` ≥ 0.2.1 and an `ai` version with
|
|
280
|
+
`experimental_evaluate` — both imported lazily, so callers who don't use
|
|
281
|
+
`decisions` are unaffected.
|
|
282
|
+
|
|
254
283
|
### `ai.begin` / `ai.settle` — long async jobs (video)
|
|
255
284
|
|
|
256
285
|
A video job is submit → wait minutes → poll/webhook → done, spanning multiple
|
|
@@ -282,6 +311,8 @@ ai.registerWebhook(http, {
|
|
|
282
311
|
|
|
283
312
|
`begin` returns the admission result (it doesn't throw — check `allowed`).
|
|
284
313
|
`settle` is idempotent (exactly-once server-side), so a retried webhook is safe.
|
|
314
|
+
The webhook resolver receives the original, unread `Request`, allowing signature
|
|
315
|
+
verification over the exact raw body; the parsed JSON comes from a clone.
|
|
285
316
|
|
|
286
317
|
### Replay
|
|
287
318
|
|
|
@@ -315,6 +346,25 @@ different dimensions. Each exposes:
|
|
|
315
346
|
The identifier field is `userId` for `ai.users`, `name` for `ai.actions`, and
|
|
316
347
|
`value` for `ai.tag(d)`. For example: `ai.tag("customer").setLimits(ctx, { value: "acme", … })`.
|
|
317
348
|
|
|
349
|
+
### Request rate limits
|
|
350
|
+
|
|
351
|
+
`requestsPerMinute` uses `@convex-dev/rate-limiter` 0.4.0 with a transactional
|
|
352
|
+
**token bucket** for each user, action, or custom tag. A limit of 60 allows an
|
|
353
|
+
initial burst of 60 requests and then refills at one request per second, up to
|
|
354
|
+
60. Zero blocks all requests. Only admitted requests consume capacity: a budget,
|
|
355
|
+
concurrency, or another bucket's rate rejection consumes none.
|
|
356
|
+
|
|
357
|
+
This replaces the previous rolling 60-second request-log count. On upgrading,
|
|
358
|
+
rate balances start full; historical requests are not imported. Budget balances,
|
|
359
|
+
reservations, and spend history are preserved. Deploy the component update to
|
|
360
|
+
mount its nested Rate Limiter component; applications do not register it separately.
|
|
361
|
+
Changing a rate keeps its existing balance (clamped to the new capacity when
|
|
362
|
+
checked); deleting and recreating a bucket starts a fresh balance.
|
|
363
|
+
|
|
364
|
+
Rate limits remain transactional. Rate Limiter's asynchronous mode is not enabled
|
|
365
|
+
here because simultaneous admissions must respect every configured bucket.
|
|
366
|
+
Global spend accounting continues to use the existing sharded counter.
|
|
367
|
+
|
|
318
368
|
### Setting caps
|
|
319
369
|
|
|
320
370
|
```ts
|
|
@@ -549,21 +599,26 @@ this design). `ai-budget` instead **reserves then settles**:
|
|
|
549
599
|
schedules a fold of the real cost into the totals, releasing the reservation —
|
|
550
600
|
so a request is never orphaned mid-flight.
|
|
551
601
|
3. **Reconcile.** A once-a-minute cron folds any stragglers and releases
|
|
552
|
-
reservations
|
|
553
|
-
|
|
554
|
-
|
|
602
|
+
expired reservations using indexed deadlines. Expiry releases the hold but
|
|
603
|
+
leaves billing open: a late completion records its final charge once without
|
|
604
|
+
releasing the hold again. After content retention, unresolved requests retain
|
|
605
|
+
a small billing record with prompts and responses removed.
|
|
555
606
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
607
|
+
Requests record the exact bucket IDs and calendar windows they reserve.
|
|
608
|
+
Settlement cannot release another request's hold, including across midnight or
|
|
609
|
+
month boundaries. Admission reads separate policy documents; only capped buckets
|
|
610
|
+
require reads of accounting state. Reporting updates still share bucket totals,
|
|
611
|
+
but do not write the policy documents used by uncapped admission.
|
|
559
612
|
|
|
560
613
|
**One admission rule, all scopes.** Every per-bucket cap — user, action, or any
|
|
561
614
|
tag — runs through the *same* admission check: a request is admitted only when
|
|
562
615
|
`committed + reserved + estimate ≤ cap` (bumps included) for **every** bucket it
|
|
563
616
|
touches. Each per-bucket cap reserves on a single document, making concurrent
|
|
564
617
|
admission atomic. The **global** killswitch is backed by a sharded counter for
|
|
565
|
-
throughput
|
|
566
|
-
reservation, so
|
|
618
|
+
throughput. Its sum is transactional but excludes unfinished and not-yet-folded
|
|
619
|
+
requests and has no cross-request reservation, so global admissions can overshoot.
|
|
620
|
+
Global usage is recorded even when limits are disabled. Historical usage omitted
|
|
621
|
+
by older versions is not automatically reconstructed by this upgrade.
|
|
567
622
|
|
|
568
623
|
Reservations are estimates, not provider-side maximum charges. If a response uses
|
|
569
624
|
more tokens or costs more than estimated, settlement records the real amount and
|
|
@@ -651,6 +706,16 @@ handles rather than taking a dependency on either sibling component.
|
|
|
651
706
|
|
|
652
707
|
---
|
|
653
708
|
|
|
709
|
+
|
|
710
|
+
## Accounting upgrade notes
|
|
711
|
+
|
|
712
|
+
New request fields are optional for existing deployments. Policy documents are
|
|
713
|
+
created on first admission or limit update; pending requests without deadlines
|
|
714
|
+
are migrated in batches by reconciliation. Existing reservations without ownership
|
|
715
|
+
metadata use their creation period and current capped buckets as a compatibility
|
|
716
|
+
fallback. Exact historical hold ownership cannot be reconstructed if those caps
|
|
717
|
+
changed before the upgrade. New requests always store explicit ownership.
|
|
718
|
+
|
|
654
719
|
## Development
|
|
655
720
|
|
|
656
721
|
```sh
|
package/dist/client/index.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ export type SoftLimitInfo = BudgetEventInfo & {
|
|
|
39
39
|
};
|
|
40
40
|
export type AIBudgetOptions = {
|
|
41
41
|
defaultModel?: string;
|
|
42
|
+
/** Default model for `decisions()` (the Decisions/"Jev" endpoint). */
|
|
43
|
+
defaultEvalModel?: string;
|
|
42
44
|
/**
|
|
43
45
|
* A *soft* limit was exceeded (request still allowed). Lets you surface budget
|
|
44
46
|
* warnings even on the languageModel/Agent path where they can't be returned.
|
|
@@ -82,8 +84,19 @@ export type ChatResult = {
|
|
|
82
84
|
/** Approaching-cap notices (empty unless a warnAtPct threshold was crossed). */
|
|
83
85
|
notices: string[];
|
|
84
86
|
};
|
|
87
|
+
/** The tracked result of a `decisions()` call: budgeting metadata plus the
|
|
88
|
+
* structured answers from the Decisions ("Jev") endpoint. */
|
|
89
|
+
export type DecisionResult = Omit<ChatResult, "text"> & {
|
|
90
|
+
/** Structured answers keyed by your question names (shape depends on each
|
|
91
|
+
* question type: `choice`, `score`, or `boolean`). */
|
|
92
|
+
answers: Record<string, any>;
|
|
93
|
+
/** The raw gateway response, including provider-specific fields (e.g.
|
|
94
|
+
* `confidence`) under `response.body`. */
|
|
95
|
+
response?: any;
|
|
96
|
+
};
|
|
85
97
|
/** Limits/controls settable on any budget bucket (user, action, or tag). */
|
|
86
98
|
export type BucketLimits = {
|
|
99
|
+
/** Token-bucket refill per minute and burst capacity; 0 blocks all requests. */
|
|
87
100
|
requestsPerMinute?: number;
|
|
88
101
|
maxConcurrent?: number;
|
|
89
102
|
dailySpendLimitNanos?: number;
|
|
@@ -106,6 +119,7 @@ export type BumpArgs = {
|
|
|
106
119
|
export declare class AIBudget {
|
|
107
120
|
component: AIBudgetApi;
|
|
108
121
|
defaultModel: string;
|
|
122
|
+
defaultEvalModel: string;
|
|
109
123
|
private onSoftLimit?;
|
|
110
124
|
private onThreshold?;
|
|
111
125
|
private onLimitReached?;
|
|
@@ -207,6 +221,46 @@ export declare class AIBudget {
|
|
|
207
221
|
/** Extra attribution dimensions to bill/limit (team, customer, env, …). */
|
|
208
222
|
tags?: Tag[];
|
|
209
223
|
}): Promise<ChatResult>;
|
|
224
|
+
/**
|
|
225
|
+
* Budget a structured decision through the AI Gateway's Decisions ("Jev")
|
|
226
|
+
* endpoint — sugar over `meter`. Evaluates typed `questions` (choice / score /
|
|
227
|
+
* boolean) about the `state` you provide, with the same reserve→settle
|
|
228
|
+
* limits, audit log, cost tracking, and per-tag attribution as `chat`. Call
|
|
229
|
+
* from an action. `userId` defaults to the authenticated caller.
|
|
230
|
+
*
|
|
231
|
+
* Requires `@convex-dev/ai-sdk-provider` >= 0.2.1 and an `ai` version that
|
|
232
|
+
* exposes `experimental_evaluate` (AI SDK 7's evaluation interface); both are
|
|
233
|
+
* imported lazily, so consumers who never call `decisions()` are unaffected.
|
|
234
|
+
*
|
|
235
|
+
* const { answers } = await ai.decisions(ctx, {
|
|
236
|
+
* state: { ticket: "Customer cannot sign in" },
|
|
237
|
+
* questions: {
|
|
238
|
+
* priority: { type: "choice", instructions: "...", criteria: { urgent: "...", normal: "..." } },
|
|
239
|
+
* needsReview: { type: "boolean", instructions: "..." },
|
|
240
|
+
* },
|
|
241
|
+
* });
|
|
242
|
+
* answers.priority.choice; // "urgent" | "normal"
|
|
243
|
+
*/
|
|
244
|
+
decisions(ctx: RunMutationCtx, args: {
|
|
245
|
+
/** The evaluation model. Defaults to `defaultEvalModel` ("typesafe/jev-1.13"). */
|
|
246
|
+
model?: string;
|
|
247
|
+
/** Context the questions are evaluated against (a string or an object). */
|
|
248
|
+
state: unknown;
|
|
249
|
+
/** Typed questions (choice / score / boolean) keyed by name. */
|
|
250
|
+
questions: Record<string, unknown>;
|
|
251
|
+
/** Whom to bill. Defaults to the authenticated user (ctx.auth). */
|
|
252
|
+
userId?: string;
|
|
253
|
+
/** Attribute spend to this action name. Defaults to the calling action. */
|
|
254
|
+
action?: string;
|
|
255
|
+
/** Extra attribution dimensions to bill/limit (team, customer, env, …). */
|
|
256
|
+
tags?: Tag[];
|
|
257
|
+
/** Reserve this exact amount (nanodollars) up front — the decision cost
|
|
258
|
+
* isn't known before the call, so a hard cap is only exact with this. */
|
|
259
|
+
estimatedCostNanos?: number;
|
|
260
|
+
rerunOf?: string;
|
|
261
|
+
/** Cancel the underlying request. */
|
|
262
|
+
abortSignal?: AbortSignal;
|
|
263
|
+
}): Promise<DecisionResult>;
|
|
210
264
|
/**
|
|
211
265
|
* An AI SDK LanguageModel that enforces limits and records usage/cost for
|
|
212
266
|
* `userId` on every call. Drop it into `generateText`, `streamText`, or the
|
|
@@ -237,6 +291,14 @@ export declare class AIBudget {
|
|
|
237
291
|
dimension: string;
|
|
238
292
|
value: string;
|
|
239
293
|
}[] | undefined;
|
|
294
|
+
heldBucketIds?: string[] | undefined;
|
|
295
|
+
reservationDay?: string | undefined;
|
|
296
|
+
reservationMonth?: string | undefined;
|
|
297
|
+
reservationReleased?: boolean | undefined;
|
|
298
|
+
reservationExpired?: boolean | undefined;
|
|
299
|
+
contentPurged?: boolean | undefined;
|
|
300
|
+
expiresAt?: number | undefined;
|
|
301
|
+
finishedAt?: number | undefined;
|
|
240
302
|
estimatedNanos?: number | undefined;
|
|
241
303
|
estimatedTokens?: number | undefined;
|
|
242
304
|
unpricedModel?: boolean | undefined;
|
|
@@ -273,6 +335,14 @@ export declare class AIBudget {
|
|
|
273
335
|
dimension: string;
|
|
274
336
|
value: string;
|
|
275
337
|
}[] | undefined;
|
|
338
|
+
heldBucketIds?: string[] | undefined;
|
|
339
|
+
reservationDay?: string | undefined;
|
|
340
|
+
reservationMonth?: string | undefined;
|
|
341
|
+
reservationReleased?: boolean | undefined;
|
|
342
|
+
reservationExpired?: boolean | undefined;
|
|
343
|
+
contentPurged?: boolean | undefined;
|
|
344
|
+
expiresAt?: number | undefined;
|
|
345
|
+
finishedAt?: number | undefined;
|
|
276
346
|
estimatedNanos?: number | undefined;
|
|
277
347
|
estimatedTokens?: number | undefined;
|
|
278
348
|
unpricedModel?: boolean | undefined;
|
|
@@ -310,6 +380,14 @@ export declare class AIBudget {
|
|
|
310
380
|
dimension: string;
|
|
311
381
|
value: string;
|
|
312
382
|
}[] | undefined;
|
|
383
|
+
heldBucketIds?: string[] | undefined;
|
|
384
|
+
reservationDay?: string | undefined;
|
|
385
|
+
reservationMonth?: string | undefined;
|
|
386
|
+
reservationReleased?: boolean | undefined;
|
|
387
|
+
reservationExpired?: boolean | undefined;
|
|
388
|
+
contentPurged?: boolean | undefined;
|
|
389
|
+
expiresAt?: number | undefined;
|
|
390
|
+
finishedAt?: number | undefined;
|
|
313
391
|
estimatedNanos?: number | undefined;
|
|
314
392
|
estimatedTokens?: number | undefined;
|
|
315
393
|
unpricedModel?: boolean | undefined;
|
|
@@ -343,6 +421,14 @@ export declare class AIBudget {
|
|
|
343
421
|
dimension: string;
|
|
344
422
|
value: string;
|
|
345
423
|
}[] | undefined;
|
|
424
|
+
heldBucketIds?: string[] | undefined;
|
|
425
|
+
reservationDay?: string | undefined;
|
|
426
|
+
reservationMonth?: string | undefined;
|
|
427
|
+
reservationReleased?: boolean | undefined;
|
|
428
|
+
reservationExpired?: boolean | undefined;
|
|
429
|
+
contentPurged?: boolean | undefined;
|
|
430
|
+
expiresAt?: number | undefined;
|
|
431
|
+
finishedAt?: number | undefined;
|
|
346
432
|
estimatedNanos?: number | undefined;
|
|
347
433
|
estimatedTokens?: number | undefined;
|
|
348
434
|
unpricedModel?: boolean | undefined;
|
package/dist/client/index.js
CHANGED
|
@@ -115,12 +115,14 @@ function timingSafeEqual(a, b) {
|
|
|
115
115
|
export class AIBudget {
|
|
116
116
|
component;
|
|
117
117
|
defaultModel;
|
|
118
|
+
defaultEvalModel;
|
|
118
119
|
onSoftLimit;
|
|
119
120
|
onThreshold;
|
|
120
121
|
onLimitReached;
|
|
121
122
|
constructor(component, options) {
|
|
122
123
|
this.component = component;
|
|
123
124
|
this.defaultModel = options?.defaultModel ?? "openai/gpt-4o-mini";
|
|
125
|
+
this.defaultEvalModel = options?.defaultEvalModel ?? "typesafe/jev-1.13";
|
|
124
126
|
this.onSoftLimit = options?.onSoftLimit;
|
|
125
127
|
this.onThreshold = options?.onThreshold;
|
|
126
128
|
this.onLimitReached = options?.onLimitReached;
|
|
@@ -302,6 +304,72 @@ export class AIBudget {
|
|
|
302
304
|
};
|
|
303
305
|
});
|
|
304
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* Budget a structured decision through the AI Gateway's Decisions ("Jev")
|
|
309
|
+
* endpoint — sugar over `meter`. Evaluates typed `questions` (choice / score /
|
|
310
|
+
* boolean) about the `state` you provide, with the same reserve→settle
|
|
311
|
+
* limits, audit log, cost tracking, and per-tag attribution as `chat`. Call
|
|
312
|
+
* from an action. `userId` defaults to the authenticated caller.
|
|
313
|
+
*
|
|
314
|
+
* Requires `@convex-dev/ai-sdk-provider` >= 0.2.1 and an `ai` version that
|
|
315
|
+
* exposes `experimental_evaluate` (AI SDK 7's evaluation interface); both are
|
|
316
|
+
* imported lazily, so consumers who never call `decisions()` are unaffected.
|
|
317
|
+
*
|
|
318
|
+
* const { answers } = await ai.decisions(ctx, {
|
|
319
|
+
* state: { ticket: "Customer cannot sign in" },
|
|
320
|
+
* questions: {
|
|
321
|
+
* priority: { type: "choice", instructions: "...", criteria: { urgent: "...", normal: "..." } },
|
|
322
|
+
* needsReview: { type: "boolean", instructions: "..." },
|
|
323
|
+
* },
|
|
324
|
+
* });
|
|
325
|
+
* answers.priority.choice; // "urgent" | "normal"
|
|
326
|
+
*/
|
|
327
|
+
async decisions(ctx, args) {
|
|
328
|
+
const model = args.model ?? this.defaultEvalModel;
|
|
329
|
+
// `evaluate` is an experimental, version-gated export; import it lazily and
|
|
330
|
+
// untyped so consumers on an older `ai` (who never call this) aren't broken.
|
|
331
|
+
const evaluate = (await import("ai")).experimental_evaluate;
|
|
332
|
+
if (typeof evaluate !== "function") {
|
|
333
|
+
throw new Error("ai-budget: decisions() needs `experimental_evaluate` from the `ai` " +
|
|
334
|
+
"package (AI SDK 7's evaluation interface). Upgrade `ai` to a " +
|
|
335
|
+
"version that exports it.");
|
|
336
|
+
}
|
|
337
|
+
// Likewise, `evaluationModel` exists on @convex-dev/ai-sdk-provider >= 0.2.1.
|
|
338
|
+
const evaluationModel = convexGateway.evaluationModel;
|
|
339
|
+
if (typeof evaluationModel !== "function") {
|
|
340
|
+
throw new Error("ai-budget: decisions() needs `convexGateway.evaluationModel` from " +
|
|
341
|
+
"@convex-dev/ai-sdk-provider >= 0.2.1. Upgrade the provider.");
|
|
342
|
+
}
|
|
343
|
+
let decision;
|
|
344
|
+
const result = await this.meter(ctx, {
|
|
345
|
+
model,
|
|
346
|
+
// Store the structured request for audit/replay.
|
|
347
|
+
messages: [
|
|
348
|
+
{
|
|
349
|
+
role: "user",
|
|
350
|
+
content: JSON.stringify({ state: args.state, questions: args.questions }),
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
userId: args.userId,
|
|
354
|
+
action: args.action,
|
|
355
|
+
tags: args.tags,
|
|
356
|
+
estimatedCostNanos: args.estimatedCostNanos,
|
|
357
|
+
rerunOf: args.rerunOf,
|
|
358
|
+
}, async () => {
|
|
359
|
+
decision = await evaluate({
|
|
360
|
+
model: evaluationModel(model),
|
|
361
|
+
state: args.state,
|
|
362
|
+
questions: args.questions,
|
|
363
|
+
...(args.abortSignal ? { abortSignal: args.abortSignal } : {}),
|
|
364
|
+
});
|
|
365
|
+
return {
|
|
366
|
+
usage: decision?.usage,
|
|
367
|
+
costNanos: extractGatewayCostNanos(decision),
|
|
368
|
+
};
|
|
369
|
+
});
|
|
370
|
+
const { text: _text, ...tracking } = result;
|
|
371
|
+
return { ...tracking, answers: decision?.answers ?? {}, response: decision?.response };
|
|
372
|
+
}
|
|
305
373
|
/**
|
|
306
374
|
* An AI SDK LanguageModel that enforces limits and records usage/cost for
|
|
307
375
|
* `userId` on every call. Drop it into `generateText`, `streamText`, or the
|
|
@@ -692,7 +760,7 @@ export class AIBudget {
|
|
|
692
760
|
path,
|
|
693
761
|
method: "POST",
|
|
694
762
|
handler: httpActionGeneric(async (ctx, request) => {
|
|
695
|
-
const body = await request.json().catch(() => ({}));
|
|
763
|
+
const body = await request.clone().json().catch(() => ({}));
|
|
696
764
|
const settle = await opts.resolve(ctx, request, body);
|
|
697
765
|
if (!settle)
|
|
698
766
|
return new Response("ignored", { status: 202 });
|
|
@@ -33,5 +33,6 @@ export declare const api: FilterApi<typeof fullApi, FunctionReference<any, "publ
|
|
|
33
33
|
export declare const internal: FilterApi<typeof fullApi, FunctionReference<any, "internal">>;
|
|
34
34
|
export declare const components: {
|
|
35
35
|
shardedCounter: import("@convex-dev/sharded-counter/_generated/component.js").ComponentApi<"shardedCounter">;
|
|
36
|
+
rateLimiter: import("@convex-dev/rate-limiter/_generated/component.js").ComponentApi<"rateLimiter">;
|
|
36
37
|
};
|
|
37
38
|
export {};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { defineComponent } from "convex/server";
|
|
2
2
|
import shardedCounter from "@convex-dev/sharded-counter/convex.config";
|
|
3
|
+
import rateLimiter from "@convex-dev/rate-limiter/convex.config";
|
|
3
4
|
const component = defineComponent("aiBudget");
|
|
4
5
|
// Global spend totals use a sharded counter for high write throughput.
|
|
5
6
|
component.use(shardedCounter);
|
|
7
|
+
component.use(rateLimiter);
|
|
6
8
|
export default component;
|
package/dist/component/lib.d.ts
CHANGED
|
@@ -58,6 +58,14 @@ export declare const lineage: import("convex/server").RegisteredQuery<"public",
|
|
|
58
58
|
dimension: string;
|
|
59
59
|
value: string;
|
|
60
60
|
}[] | undefined;
|
|
61
|
+
heldBucketIds?: import("convex/values").GenericId<"buckets">[] | undefined;
|
|
62
|
+
reservationDay?: string | undefined;
|
|
63
|
+
reservationMonth?: string | undefined;
|
|
64
|
+
reservationReleased?: boolean | undefined;
|
|
65
|
+
reservationExpired?: boolean | undefined;
|
|
66
|
+
contentPurged?: boolean | undefined;
|
|
67
|
+
expiresAt?: number | undefined;
|
|
68
|
+
finishedAt?: number | undefined;
|
|
61
69
|
estimatedNanos?: number | undefined;
|
|
62
70
|
estimatedTokens?: number | undefined;
|
|
63
71
|
unpricedModel?: boolean | undefined;
|
|
@@ -89,6 +97,14 @@ export declare const lineage: import("convex/server").RegisteredQuery<"public",
|
|
|
89
97
|
dimension: string;
|
|
90
98
|
value: string;
|
|
91
99
|
}[] | undefined;
|
|
100
|
+
heldBucketIds?: import("convex/values").GenericId<"buckets">[] | undefined;
|
|
101
|
+
reservationDay?: string | undefined;
|
|
102
|
+
reservationMonth?: string | undefined;
|
|
103
|
+
reservationReleased?: boolean | undefined;
|
|
104
|
+
reservationExpired?: boolean | undefined;
|
|
105
|
+
contentPurged?: boolean | undefined;
|
|
106
|
+
expiresAt?: number | undefined;
|
|
107
|
+
finishedAt?: number | undefined;
|
|
92
108
|
estimatedNanos?: number | undefined;
|
|
93
109
|
estimatedTokens?: number | undefined;
|
|
94
110
|
unpricedModel?: boolean | undefined;
|
|
@@ -123,6 +139,14 @@ export declare const getRequest: import("convex/server").RegisteredQuery<"public
|
|
|
123
139
|
dimension: string;
|
|
124
140
|
value: string;
|
|
125
141
|
}[] | undefined;
|
|
142
|
+
heldBucketIds?: import("convex/values").GenericId<"buckets">[] | undefined;
|
|
143
|
+
reservationDay?: string | undefined;
|
|
144
|
+
reservationMonth?: string | undefined;
|
|
145
|
+
reservationReleased?: boolean | undefined;
|
|
146
|
+
reservationExpired?: boolean | undefined;
|
|
147
|
+
contentPurged?: boolean | undefined;
|
|
148
|
+
expiresAt?: number | undefined;
|
|
149
|
+
finishedAt?: number | undefined;
|
|
126
150
|
estimatedNanos?: number | undefined;
|
|
127
151
|
estimatedTokens?: number | undefined;
|
|
128
152
|
unpricedModel?: boolean | undefined;
|
|
@@ -159,6 +183,14 @@ export declare const listRequests: import("convex/server").RegisteredQuery<"publ
|
|
|
159
183
|
dimension: string;
|
|
160
184
|
value: string;
|
|
161
185
|
}[] | undefined;
|
|
186
|
+
heldBucketIds?: import("convex/values").GenericId<"buckets">[] | undefined;
|
|
187
|
+
reservationDay?: string | undefined;
|
|
188
|
+
reservationMonth?: string | undefined;
|
|
189
|
+
reservationReleased?: boolean | undefined;
|
|
190
|
+
reservationExpired?: boolean | undefined;
|
|
191
|
+
contentPurged?: boolean | undefined;
|
|
192
|
+
expiresAt?: number | undefined;
|
|
193
|
+
finishedAt?: number | undefined;
|
|
162
194
|
estimatedNanos?: number | undefined;
|
|
163
195
|
estimatedTokens?: number | undefined;
|
|
164
196
|
unpricedModel?: boolean | undefined;
|