@convex-dev/ai-budget 0.0.2-alpha.0 → 0.0.2-alpha.12
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 +388 -185
- package/dist/client/dashboard.d.ts +1 -0
- package/dist/client/dashboard.js +223 -0
- package/dist/client/index.d.ts +545 -38
- package/dist/client/index.js +343 -59
- package/dist/component/_generated/component.d.ts +62 -24
- package/dist/component/lib.d.ts +144 -40
- package/dist/component/lib.js +648 -305
- package/dist/component/schema.d.ts +107 -53
- package/dist/component/schema.js +87 -38
- package/package.json +5 -5
- package/src/client/dashboard.ts +223 -0
- package/src/client/index.ts +513 -109
- package/src/component/_generated/component.ts +94 -29
- package/src/component/lib.test.ts +253 -15
- package/src/component/lib.ts +746 -333
- package/src/component/schema.ts +90 -38
package/dist/client/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Expand, type FunctionReference, type HttpRouter } from "convex/server";
|
|
2
2
|
import { type GenericId } from "convex/values";
|
|
3
3
|
import { type LanguageModel } from "ai";
|
|
4
4
|
import type { api } from "../component/_generated/api";
|
|
@@ -11,21 +11,44 @@ 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
|
-
|
|
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
|
+
};
|
|
24
|
+
/** Common shape for budget-event callbacks. */
|
|
25
|
+
export type BudgetEventInfo = {
|
|
16
26
|
userId: string;
|
|
17
27
|
action?: string;
|
|
18
|
-
|
|
28
|
+
tags?: Tag[];
|
|
29
|
+
requestId?: string;
|
|
30
|
+
/** soft-cap warnings (onSoftLimit) or approaching-cap notices (onThreshold). */
|
|
31
|
+
messages: string[];
|
|
32
|
+
/** rejection code/reason (onLimitReached only). */
|
|
33
|
+
code?: string;
|
|
34
|
+
reason?: string;
|
|
35
|
+
};
|
|
36
|
+
/** @deprecated use BudgetEventInfo */
|
|
37
|
+
export type SoftLimitInfo = BudgetEventInfo & {
|
|
19
38
|
warnings: string[];
|
|
20
39
|
};
|
|
21
40
|
export type AIBudgetOptions = {
|
|
22
41
|
defaultModel?: string;
|
|
23
42
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
43
|
+
* A *soft* limit was exceeded (request still allowed). Lets you surface budget
|
|
44
|
+
* warnings even on the languageModel/Agent path where they can't be returned.
|
|
45
|
+
* Errors thrown in any of these callbacks are swallowed.
|
|
27
46
|
*/
|
|
28
47
|
onSoftLimit?: (info: SoftLimitInfo) => void | Promise<void>;
|
|
48
|
+
/** Usage crossed a bucket's warnAtPct threshold (approaching a cap). */
|
|
49
|
+
onThreshold?: (info: BudgetEventInfo) => void | Promise<void>;
|
|
50
|
+
/** A *hard* limit blocked the request (fires just before chat/model throws). */
|
|
51
|
+
onLimitReached?: (info: BudgetEventInfo) => void | Promise<void>;
|
|
29
52
|
};
|
|
30
53
|
type RunQueryCtx = {
|
|
31
54
|
runQuery: <Query extends FunctionReference<"query", "internal">>(query: Query, args: Query["_args"]) => Promise<Query["_returnType"]>;
|
|
@@ -56,16 +79,69 @@ export type ChatResult = {
|
|
|
56
79
|
cachedTokens: number;
|
|
57
80
|
/** Soft-limit warnings raised at admission (empty unless a soft cap was hit). */
|
|
58
81
|
warnings: string[];
|
|
82
|
+
/** Approaching-cap notices (empty unless a warnAtPct threshold was crossed). */
|
|
83
|
+
notices: string[];
|
|
84
|
+
};
|
|
85
|
+
/** Limits/controls settable on any budget bucket (user, action, or tag). */
|
|
86
|
+
export type BucketLimits = {
|
|
87
|
+
requestsPerMinute?: number;
|
|
88
|
+
maxConcurrent?: number;
|
|
89
|
+
dailySpendLimitNanos?: number;
|
|
90
|
+
monthlySpendLimitNanos?: number;
|
|
91
|
+
lifetimeSpendLimitNanos?: number;
|
|
92
|
+
dailyTokenLimit?: number;
|
|
93
|
+
monthlyTokenLimit?: number;
|
|
94
|
+
lifetimeTokenLimit?: number;
|
|
95
|
+
/** Fire an approaching-limit alert at this fraction of a cap (e.g. 0.8). */
|
|
96
|
+
warnAtPct?: number;
|
|
97
|
+
enforcement?: "hard" | "soft";
|
|
98
|
+
blocked?: boolean;
|
|
99
|
+
};
|
|
100
|
+
/** One-time bump amounts, added on top of a standing cap. */
|
|
101
|
+
export type BumpArgs = {
|
|
102
|
+
dailyNanos?: number;
|
|
103
|
+
monthlyNanos?: number;
|
|
104
|
+
lifetimeNanos?: number;
|
|
59
105
|
};
|
|
60
106
|
export declare class AIBudget {
|
|
61
107
|
component: AIBudgetApi;
|
|
62
108
|
defaultModel: string;
|
|
63
109
|
private onSoftLimit?;
|
|
110
|
+
private onThreshold?;
|
|
111
|
+
private onLimitReached?;
|
|
64
112
|
constructor(component: AIBudgetApi, options?: AIBudgetOptions);
|
|
65
|
-
private
|
|
113
|
+
private fireBudgetEvents;
|
|
114
|
+
private fireLimitReached;
|
|
115
|
+
/**
|
|
116
|
+
* Meter ANY LLM call — gateway, a provider SDK, a raw fetch — with the same
|
|
117
|
+
* budgets, audit log, and cost tracking. Reserves before your `run` (throwing
|
|
118
|
+
* a ConvexError over a hard cap), runs it, then records the actual usage/cost.
|
|
119
|
+
* This is the provider-agnostic core; `chat` is sugar over it for the gateway.
|
|
120
|
+
*
|
|
121
|
+
* `run` returns what happened. Pass a raw provider `usage` object (auto-
|
|
122
|
+
* normalized) OR explicit `promptTokens`/`completionTokens`/`cachedTokens`,
|
|
123
|
+
* plus optional `serverToolUses` (e.g. `{ web_search: 3 }`, priced on top of
|
|
124
|
+
* tokens) and an authoritative `costNanos` (used verbatim if present).
|
|
125
|
+
*/
|
|
126
|
+
meter(ctx: RunMutationCtx, opts: {
|
|
127
|
+
model: string;
|
|
128
|
+
messages: Message[];
|
|
129
|
+
userId?: string;
|
|
130
|
+
action?: string;
|
|
131
|
+
tags?: Tag[];
|
|
132
|
+
rerunOf?: string;
|
|
133
|
+
}, run: () => Promise<{
|
|
134
|
+
text?: string;
|
|
135
|
+
usage?: any;
|
|
136
|
+
promptTokens?: number;
|
|
137
|
+
completionTokens?: number;
|
|
138
|
+
cachedTokens?: number;
|
|
139
|
+
serverToolUses?: Record<string, number>;
|
|
140
|
+
costNanos?: number;
|
|
141
|
+
}>): Promise<ChatResult>;
|
|
66
142
|
/**
|
|
67
|
-
* One-shot chat through the AI Gateway with tracking + limits
|
|
68
|
-
* Call from an action. `userId` defaults to the authenticated caller.
|
|
143
|
+
* One-shot chat through the AI Gateway with tracking + limits — sugar over
|
|
144
|
+
* `meter`. Call from an action. `userId` defaults to the authenticated caller.
|
|
69
145
|
*/
|
|
70
146
|
chat(ctx: RunMutationCtx, args?: {
|
|
71
147
|
/** Whom to bill. Defaults to the authenticated user (ctx.auth). */
|
|
@@ -76,6 +152,8 @@ export declare class AIBudget {
|
|
|
76
152
|
rerunOf?: string;
|
|
77
153
|
/** Attribute spend to this action name. Defaults to the calling Convex action. */
|
|
78
154
|
action?: string;
|
|
155
|
+
/** Extra attribution dimensions to bill/limit (team, customer, env, …). */
|
|
156
|
+
tags?: Tag[];
|
|
79
157
|
}): Promise<ChatResult>;
|
|
80
158
|
/**
|
|
81
159
|
* An AI SDK LanguageModel that enforces limits and records usage/cost for
|
|
@@ -87,17 +165,26 @@ export declare class AIBudget {
|
|
|
87
165
|
userId?: string;
|
|
88
166
|
model?: string;
|
|
89
167
|
action?: string;
|
|
168
|
+
/** Extra attribution dimensions to bill/limit (team, customer, env, …). */
|
|
169
|
+
tags?: Tag[];
|
|
90
170
|
}): LanguageModel;
|
|
91
171
|
private rerunImpl;
|
|
92
172
|
/** The request audit log, replay, and re-run lineage. */
|
|
93
173
|
get requests(): {
|
|
174
|
+
/** Filter by userId, or by any {dimension, value} (incl. custom tags). */
|
|
94
175
|
list: (ctx: RunQueryCtx, args?: {
|
|
95
176
|
userId?: string;
|
|
177
|
+
dimension?: string;
|
|
178
|
+
value?: string;
|
|
96
179
|
limit?: number;
|
|
97
180
|
}) => Promise<{
|
|
98
181
|
_id: string;
|
|
99
182
|
_creationTime: number;
|
|
100
183
|
actionName?: string | undefined;
|
|
184
|
+
tags?: {
|
|
185
|
+
dimension: string;
|
|
186
|
+
value: string;
|
|
187
|
+
}[] | undefined;
|
|
101
188
|
estimatedNanos?: number | undefined;
|
|
102
189
|
estimatedTokens?: number | undefined;
|
|
103
190
|
unpricedModel?: boolean | undefined;
|
|
@@ -108,6 +195,9 @@ export declare class AIBudget {
|
|
|
108
195
|
promptTokens?: number | undefined;
|
|
109
196
|
completionTokens?: number | undefined;
|
|
110
197
|
cachedTokens?: number | undefined;
|
|
198
|
+
serverToolUses?: {
|
|
199
|
+
[x: string]: number;
|
|
200
|
+
} | undefined;
|
|
111
201
|
costNanos?: number | undefined;
|
|
112
202
|
latencyMs?: number | undefined;
|
|
113
203
|
rerunOf?: string | undefined;
|
|
@@ -119,6 +209,41 @@ export declare class AIBudget {
|
|
|
119
209
|
}[];
|
|
120
210
|
status: "blocked" | "pending" | "success" | "error";
|
|
121
211
|
}[]>;
|
|
212
|
+
/** One request, including its stored prompt and response. */
|
|
213
|
+
get: (ctx: RunQueryCtx, args: {
|
|
214
|
+
requestId: string;
|
|
215
|
+
}) => Promise<{
|
|
216
|
+
_id: string;
|
|
217
|
+
_creationTime: number;
|
|
218
|
+
actionName?: string | undefined;
|
|
219
|
+
tags?: {
|
|
220
|
+
dimension: string;
|
|
221
|
+
value: string;
|
|
222
|
+
}[] | undefined;
|
|
223
|
+
estimatedNanos?: number | undefined;
|
|
224
|
+
estimatedTokens?: number | undefined;
|
|
225
|
+
unpricedModel?: boolean | undefined;
|
|
226
|
+
overBudget?: boolean | undefined;
|
|
227
|
+
settled?: boolean | undefined;
|
|
228
|
+
error?: string | undefined;
|
|
229
|
+
responseText?: string | undefined;
|
|
230
|
+
promptTokens?: number | undefined;
|
|
231
|
+
completionTokens?: number | undefined;
|
|
232
|
+
cachedTokens?: number | undefined;
|
|
233
|
+
serverToolUses?: {
|
|
234
|
+
[x: string]: number;
|
|
235
|
+
} | undefined;
|
|
236
|
+
costNanos?: number | undefined;
|
|
237
|
+
latencyMs?: number | undefined;
|
|
238
|
+
rerunOf?: string | undefined;
|
|
239
|
+
userId: string;
|
|
240
|
+
model: string;
|
|
241
|
+
messages: {
|
|
242
|
+
role: string;
|
|
243
|
+
content: string;
|
|
244
|
+
}[];
|
|
245
|
+
status: "blocked" | "pending" | "success" | "error";
|
|
246
|
+
} | null>;
|
|
122
247
|
/** Ancestors up to the original, plus direct re-runs. */
|
|
123
248
|
lineage: (ctx: RunQueryCtx, args: {
|
|
124
249
|
requestId: string;
|
|
@@ -127,6 +252,10 @@ export declare class AIBudget {
|
|
|
127
252
|
_id: string;
|
|
128
253
|
_creationTime: number;
|
|
129
254
|
actionName?: string | undefined;
|
|
255
|
+
tags?: {
|
|
256
|
+
dimension: string;
|
|
257
|
+
value: string;
|
|
258
|
+
}[] | undefined;
|
|
130
259
|
estimatedNanos?: number | undefined;
|
|
131
260
|
estimatedTokens?: number | undefined;
|
|
132
261
|
unpricedModel?: boolean | undefined;
|
|
@@ -137,6 +266,9 @@ export declare class AIBudget {
|
|
|
137
266
|
promptTokens?: number | undefined;
|
|
138
267
|
completionTokens?: number | undefined;
|
|
139
268
|
cachedTokens?: number | undefined;
|
|
269
|
+
serverToolUses?: {
|
|
270
|
+
[x: string]: number;
|
|
271
|
+
} | undefined;
|
|
140
272
|
costNanos?: number | undefined;
|
|
141
273
|
latencyMs?: number | undefined;
|
|
142
274
|
rerunOf?: string | undefined;
|
|
@@ -152,6 +284,10 @@ export declare class AIBudget {
|
|
|
152
284
|
_id: string;
|
|
153
285
|
_creationTime: number;
|
|
154
286
|
actionName?: string | undefined;
|
|
287
|
+
tags?: {
|
|
288
|
+
dimension: string;
|
|
289
|
+
value: string;
|
|
290
|
+
}[] | undefined;
|
|
155
291
|
estimatedNanos?: number | undefined;
|
|
156
292
|
estimatedTokens?: number | undefined;
|
|
157
293
|
unpricedModel?: boolean | undefined;
|
|
@@ -162,6 +298,9 @@ export declare class AIBudget {
|
|
|
162
298
|
promptTokens?: number | undefined;
|
|
163
299
|
completionTokens?: number | undefined;
|
|
164
300
|
cachedTokens?: number | undefined;
|
|
301
|
+
serverToolUses?: {
|
|
302
|
+
[x: string]: number;
|
|
303
|
+
} | undefined;
|
|
165
304
|
costNanos?: number | undefined;
|
|
166
305
|
latencyMs?: number | undefined;
|
|
167
306
|
rerunOf?: string | undefined;
|
|
@@ -181,51 +320,281 @@ export declare class AIBudget {
|
|
|
181
320
|
model?: string;
|
|
182
321
|
}) => Promise<ChatResult>;
|
|
183
322
|
};
|
|
184
|
-
/**
|
|
323
|
+
/**
|
|
324
|
+
* Budgets and controls for an arbitrary attribution dimension — the
|
|
325
|
+
* generalization of `users`/`actions`. Give it any dimension name (team,
|
|
326
|
+
* project, tenant, customer, env, feature, …) and set caps per value:
|
|
327
|
+
*
|
|
328
|
+
* ai.tag("customer").setLimits(ctx, { value: "acme", monthlySpendLimitNanos });
|
|
329
|
+
* ai.tag("customer").history(ctx, { value: "acme", period: "day" });
|
|
330
|
+
*
|
|
331
|
+
* Attribute a call to it by passing `tags` to `chat`/`languageModel`.
|
|
332
|
+
*/
|
|
333
|
+
tag(dimension: string): {
|
|
334
|
+
/** All buckets in this dimension. */
|
|
335
|
+
list: (ctx: RunQueryCtx) => Promise<{
|
|
336
|
+
spendTodayNanos: number;
|
|
337
|
+
spendThisMonthNanos: number;
|
|
338
|
+
_id: string;
|
|
339
|
+
_creationTime: number;
|
|
340
|
+
requestsPerMinute?: number | undefined;
|
|
341
|
+
maxConcurrent?: number | undefined;
|
|
342
|
+
dailySpendLimitNanos?: number | undefined;
|
|
343
|
+
monthlySpendLimitNanos?: number | undefined;
|
|
344
|
+
lifetimeSpendLimitNanos?: number | undefined;
|
|
345
|
+
dailyTokenLimit?: number | undefined;
|
|
346
|
+
monthlyTokenLimit?: number | undefined;
|
|
347
|
+
lifetimeTokenLimit?: number | undefined;
|
|
348
|
+
blocked?: boolean | undefined;
|
|
349
|
+
warnAtPct?: number | undefined;
|
|
350
|
+
enforcement?: "hard" | "soft" | undefined;
|
|
351
|
+
dailyBumpNanos?: number | undefined;
|
|
352
|
+
monthlyBumpNanos?: number | undefined;
|
|
353
|
+
lifetimeBumpNanos?: number | undefined;
|
|
354
|
+
bumpDayStamp?: string | undefined;
|
|
355
|
+
bumpMonthStamp?: string | undefined;
|
|
356
|
+
tokensToday?: number | undefined;
|
|
357
|
+
monthStamp?: string | undefined;
|
|
358
|
+
tokensThisMonth?: number | undefined;
|
|
359
|
+
reservedTodayNanos?: number | undefined;
|
|
360
|
+
reservedMonthNanos?: number | undefined;
|
|
361
|
+
reservedTotalNanos?: number | undefined;
|
|
362
|
+
reservedTodayTokens?: number | undefined;
|
|
363
|
+
reservedMonthTokens?: number | undefined;
|
|
364
|
+
reservedTotalTokens?: number | undefined;
|
|
365
|
+
pendingCount?: number | undefined;
|
|
366
|
+
dimension: string;
|
|
367
|
+
value: string;
|
|
368
|
+
totalSpendNanos: number;
|
|
369
|
+
totalRequests: number;
|
|
370
|
+
totalTokens: number;
|
|
371
|
+
dayStamp: string;
|
|
372
|
+
}[]>;
|
|
373
|
+
/** One bucket's limits + spend (null if it has none yet). */
|
|
374
|
+
get: (ctx: RunQueryCtx, args: {
|
|
375
|
+
value: string;
|
|
376
|
+
}) => Promise<{
|
|
377
|
+
spendTodayNanos: number;
|
|
378
|
+
spendThisMonthNanos: number;
|
|
379
|
+
_id: string;
|
|
380
|
+
_creationTime: number;
|
|
381
|
+
requestsPerMinute?: number | undefined;
|
|
382
|
+
maxConcurrent?: number | undefined;
|
|
383
|
+
dailySpendLimitNanos?: number | undefined;
|
|
384
|
+
monthlySpendLimitNanos?: number | undefined;
|
|
385
|
+
lifetimeSpendLimitNanos?: number | undefined;
|
|
386
|
+
dailyTokenLimit?: number | undefined;
|
|
387
|
+
monthlyTokenLimit?: number | undefined;
|
|
388
|
+
lifetimeTokenLimit?: number | undefined;
|
|
389
|
+
blocked?: boolean | undefined;
|
|
390
|
+
warnAtPct?: number | undefined;
|
|
391
|
+
enforcement?: "hard" | "soft" | undefined;
|
|
392
|
+
dailyBumpNanos?: number | undefined;
|
|
393
|
+
monthlyBumpNanos?: number | undefined;
|
|
394
|
+
lifetimeBumpNanos?: number | undefined;
|
|
395
|
+
bumpDayStamp?: string | undefined;
|
|
396
|
+
bumpMonthStamp?: string | undefined;
|
|
397
|
+
tokensToday?: number | undefined;
|
|
398
|
+
monthStamp?: string | undefined;
|
|
399
|
+
tokensThisMonth?: number | undefined;
|
|
400
|
+
reservedTodayNanos?: number | undefined;
|
|
401
|
+
reservedMonthNanos?: number | undefined;
|
|
402
|
+
reservedTotalNanos?: number | undefined;
|
|
403
|
+
reservedTodayTokens?: number | undefined;
|
|
404
|
+
reservedMonthTokens?: number | undefined;
|
|
405
|
+
reservedTotalTokens?: number | undefined;
|
|
406
|
+
pendingCount?: number | undefined;
|
|
407
|
+
dimension: string;
|
|
408
|
+
value: string;
|
|
409
|
+
totalSpendNanos: number;
|
|
410
|
+
totalRequests: number;
|
|
411
|
+
totalTokens: number;
|
|
412
|
+
dayStamp: string;
|
|
413
|
+
} | null>;
|
|
414
|
+
setLimits: (ctx: RunMutationCtx, args: {
|
|
415
|
+
value: string;
|
|
416
|
+
} & BucketLimits) => Promise<null>;
|
|
417
|
+
/** One-time "approve another $X" bump (daily/monthly reset with the window). */
|
|
418
|
+
bump: (ctx: RunMutationCtx, args: {
|
|
419
|
+
value: string;
|
|
420
|
+
} & BumpArgs) => Promise<null>;
|
|
421
|
+
/** Manually credit (negative) or debit (positive) this bucket. */
|
|
422
|
+
adjust: (ctx: RunMutationCtx, args: {
|
|
423
|
+
value: string;
|
|
424
|
+
} & {
|
|
425
|
+
deltaNanos: number;
|
|
426
|
+
tokens?: number;
|
|
427
|
+
reason?: string;
|
|
428
|
+
}) => Promise<null>;
|
|
429
|
+
/** Durable spend history for this bucket (per day or per month). */
|
|
430
|
+
history: (ctx: RunQueryCtx, args: {
|
|
431
|
+
value: string;
|
|
432
|
+
} & {
|
|
433
|
+
period?: "day" | "month";
|
|
434
|
+
limit?: number;
|
|
435
|
+
}) => Promise<{
|
|
436
|
+
_id: string;
|
|
437
|
+
_creationTime: number;
|
|
438
|
+
dimension: string;
|
|
439
|
+
value: string;
|
|
440
|
+
period: "day" | "month";
|
|
441
|
+
stamp: string;
|
|
442
|
+
spendNanos: number;
|
|
443
|
+
tokens: number;
|
|
444
|
+
requests: number;
|
|
445
|
+
}[]>;
|
|
446
|
+
/** Manual-adjustment audit log for this bucket. */
|
|
447
|
+
adjustments: (ctx: RunQueryCtx, args: {
|
|
448
|
+
value: string;
|
|
449
|
+
} & {
|
|
450
|
+
limit?: number;
|
|
451
|
+
}) => Promise<{
|
|
452
|
+
_id: string;
|
|
453
|
+
_creationTime: number;
|
|
454
|
+
tokens?: number | undefined;
|
|
455
|
+
reason?: string | undefined;
|
|
456
|
+
dimension: string;
|
|
457
|
+
value: string;
|
|
458
|
+
deltaNanos: number;
|
|
459
|
+
}[]>;
|
|
460
|
+
/** Delete the bucket (for "user", also its request rows). */
|
|
461
|
+
delete: (ctx: RunMutationCtx, args: {
|
|
462
|
+
value: string;
|
|
463
|
+
}) => Promise<{
|
|
464
|
+
deletedThisBatch: number;
|
|
465
|
+
done: boolean;
|
|
466
|
+
}>;
|
|
467
|
+
};
|
|
468
|
+
private dimensionApi;
|
|
469
|
+
/** Per-user budgets and controls — sugar over the "user" dimension. */
|
|
185
470
|
get users(): {
|
|
471
|
+
/** All buckets in this dimension. */
|
|
186
472
|
list: (ctx: RunQueryCtx) => Promise<{
|
|
187
473
|
spendTodayNanos: number;
|
|
474
|
+
spendThisMonthNanos: number;
|
|
188
475
|
_id: string;
|
|
189
476
|
_creationTime: number;
|
|
190
477
|
requestsPerMinute?: number | undefined;
|
|
478
|
+
maxConcurrent?: number | undefined;
|
|
191
479
|
dailySpendLimitNanos?: number | undefined;
|
|
480
|
+
monthlySpendLimitNanos?: number | undefined;
|
|
192
481
|
lifetimeSpendLimitNanos?: number | undefined;
|
|
193
482
|
dailyTokenLimit?: number | undefined;
|
|
483
|
+
monthlyTokenLimit?: number | undefined;
|
|
194
484
|
lifetimeTokenLimit?: number | undefined;
|
|
195
485
|
blocked?: boolean | undefined;
|
|
486
|
+
warnAtPct?: number | undefined;
|
|
196
487
|
enforcement?: "hard" | "soft" | undefined;
|
|
197
488
|
dailyBumpNanos?: number | undefined;
|
|
489
|
+
monthlyBumpNanos?: number | undefined;
|
|
198
490
|
lifetimeBumpNanos?: number | undefined;
|
|
199
491
|
bumpDayStamp?: string | undefined;
|
|
492
|
+
bumpMonthStamp?: string | undefined;
|
|
200
493
|
tokensToday?: number | undefined;
|
|
494
|
+
monthStamp?: string | undefined;
|
|
495
|
+
tokensThisMonth?: number | undefined;
|
|
201
496
|
reservedTodayNanos?: number | undefined;
|
|
497
|
+
reservedMonthNanos?: number | undefined;
|
|
202
498
|
reservedTotalNanos?: number | undefined;
|
|
203
499
|
reservedTodayTokens?: number | undefined;
|
|
500
|
+
reservedMonthTokens?: number | undefined;
|
|
204
501
|
reservedTotalTokens?: number | undefined;
|
|
205
502
|
pendingCount?: number | undefined;
|
|
206
|
-
|
|
503
|
+
dimension: string;
|
|
504
|
+
value: string;
|
|
207
505
|
totalSpendNanos: number;
|
|
208
506
|
totalRequests: number;
|
|
209
507
|
totalTokens: number;
|
|
210
508
|
dayStamp: string;
|
|
211
509
|
}[]>;
|
|
510
|
+
/** One bucket's limits + spend (null if it has none yet). */
|
|
511
|
+
get: (ctx: RunQueryCtx, args: {
|
|
512
|
+
userId: string;
|
|
513
|
+
}) => Promise<{
|
|
514
|
+
spendTodayNanos: number;
|
|
515
|
+
spendThisMonthNanos: number;
|
|
516
|
+
_id: string;
|
|
517
|
+
_creationTime: number;
|
|
518
|
+
requestsPerMinute?: number | undefined;
|
|
519
|
+
maxConcurrent?: number | undefined;
|
|
520
|
+
dailySpendLimitNanos?: number | undefined;
|
|
521
|
+
monthlySpendLimitNanos?: number | undefined;
|
|
522
|
+
lifetimeSpendLimitNanos?: number | undefined;
|
|
523
|
+
dailyTokenLimit?: number | undefined;
|
|
524
|
+
monthlyTokenLimit?: number | undefined;
|
|
525
|
+
lifetimeTokenLimit?: number | undefined;
|
|
526
|
+
blocked?: boolean | undefined;
|
|
527
|
+
warnAtPct?: number | undefined;
|
|
528
|
+
enforcement?: "hard" | "soft" | undefined;
|
|
529
|
+
dailyBumpNanos?: number | undefined;
|
|
530
|
+
monthlyBumpNanos?: number | undefined;
|
|
531
|
+
lifetimeBumpNanos?: number | undefined;
|
|
532
|
+
bumpDayStamp?: string | undefined;
|
|
533
|
+
bumpMonthStamp?: string | undefined;
|
|
534
|
+
tokensToday?: number | undefined;
|
|
535
|
+
monthStamp?: string | undefined;
|
|
536
|
+
tokensThisMonth?: number | undefined;
|
|
537
|
+
reservedTodayNanos?: number | undefined;
|
|
538
|
+
reservedMonthNanos?: number | undefined;
|
|
539
|
+
reservedTotalNanos?: number | undefined;
|
|
540
|
+
reservedTodayTokens?: number | undefined;
|
|
541
|
+
reservedMonthTokens?: number | undefined;
|
|
542
|
+
reservedTotalTokens?: number | undefined;
|
|
543
|
+
pendingCount?: number | undefined;
|
|
544
|
+
dimension: string;
|
|
545
|
+
value: string;
|
|
546
|
+
totalSpendNanos: number;
|
|
547
|
+
totalRequests: number;
|
|
548
|
+
totalTokens: number;
|
|
549
|
+
dayStamp: string;
|
|
550
|
+
} | null>;
|
|
212
551
|
setLimits: (ctx: RunMutationCtx, args: {
|
|
213
552
|
userId: string;
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
lifetimeSpendLimitNanos?: number;
|
|
217
|
-
dailyTokenLimit?: number;
|
|
218
|
-
lifetimeTokenLimit?: number;
|
|
219
|
-
enforcement?: "hard" | "soft";
|
|
220
|
-
blocked?: boolean;
|
|
221
|
-
}) => Promise<null>;
|
|
222
|
-
/** One-time "approve another $X" bump (daily is today-only). */
|
|
553
|
+
} & BucketLimits) => Promise<null>;
|
|
554
|
+
/** One-time "approve another $X" bump (daily/monthly reset with the window). */
|
|
223
555
|
bump: (ctx: RunMutationCtx, args: {
|
|
224
556
|
userId: string;
|
|
225
|
-
|
|
226
|
-
|
|
557
|
+
} & BumpArgs) => Promise<null>;
|
|
558
|
+
/** Manually credit (negative) or debit (positive) this bucket. */
|
|
559
|
+
adjust: (ctx: RunMutationCtx, args: {
|
|
560
|
+
userId: string;
|
|
561
|
+
} & {
|
|
562
|
+
deltaNanos: number;
|
|
563
|
+
tokens?: number;
|
|
564
|
+
reason?: string;
|
|
227
565
|
}) => Promise<null>;
|
|
228
|
-
/**
|
|
566
|
+
/** Durable spend history for this bucket (per day or per month). */
|
|
567
|
+
history: (ctx: RunQueryCtx, args: {
|
|
568
|
+
userId: string;
|
|
569
|
+
} & {
|
|
570
|
+
period?: "day" | "month";
|
|
571
|
+
limit?: number;
|
|
572
|
+
}) => Promise<{
|
|
573
|
+
_id: string;
|
|
574
|
+
_creationTime: number;
|
|
575
|
+
dimension: string;
|
|
576
|
+
value: string;
|
|
577
|
+
period: "day" | "month";
|
|
578
|
+
stamp: string;
|
|
579
|
+
spendNanos: number;
|
|
580
|
+
tokens: number;
|
|
581
|
+
requests: number;
|
|
582
|
+
}[]>;
|
|
583
|
+
/** Manual-adjustment audit log for this bucket. */
|
|
584
|
+
adjustments: (ctx: RunQueryCtx, args: {
|
|
585
|
+
userId: string;
|
|
586
|
+
} & {
|
|
587
|
+
limit?: number;
|
|
588
|
+
}) => Promise<{
|
|
589
|
+
_id: string;
|
|
590
|
+
_creationTime: number;
|
|
591
|
+
tokens?: number | undefined;
|
|
592
|
+
reason?: string | undefined;
|
|
593
|
+
dimension: string;
|
|
594
|
+
value: string;
|
|
595
|
+
deltaNanos: number;
|
|
596
|
+
}[]>;
|
|
597
|
+
/** Delete the bucket (for "user", also its request rows). */
|
|
229
598
|
delete: (ctx: RunMutationCtx, args: {
|
|
230
599
|
userId: string;
|
|
231
600
|
}) => Promise<{
|
|
@@ -233,49 +602,143 @@ export declare class AIBudget {
|
|
|
233
602
|
done: boolean;
|
|
234
603
|
}>;
|
|
235
604
|
};
|
|
236
|
-
/** Per-action (per-feature) budgets. */
|
|
605
|
+
/** Per-action (per-feature) budgets — sugar over the "action" dimension. */
|
|
237
606
|
get actions(): {
|
|
607
|
+
/** All buckets in this dimension. */
|
|
238
608
|
list: (ctx: RunQueryCtx) => Promise<{
|
|
239
609
|
spendTodayNanos: number;
|
|
610
|
+
spendThisMonthNanos: number;
|
|
240
611
|
_id: string;
|
|
241
612
|
_creationTime: number;
|
|
613
|
+
requestsPerMinute?: number | undefined;
|
|
614
|
+
maxConcurrent?: number | undefined;
|
|
242
615
|
dailySpendLimitNanos?: number | undefined;
|
|
616
|
+
monthlySpendLimitNanos?: number | undefined;
|
|
243
617
|
lifetimeSpendLimitNanos?: number | undefined;
|
|
244
618
|
dailyTokenLimit?: number | undefined;
|
|
619
|
+
monthlyTokenLimit?: number | undefined;
|
|
245
620
|
lifetimeTokenLimit?: number | undefined;
|
|
621
|
+
blocked?: boolean | undefined;
|
|
622
|
+
warnAtPct?: number | undefined;
|
|
246
623
|
enforcement?: "hard" | "soft" | undefined;
|
|
247
624
|
dailyBumpNanos?: number | undefined;
|
|
625
|
+
monthlyBumpNanos?: number | undefined;
|
|
248
626
|
lifetimeBumpNanos?: number | undefined;
|
|
249
627
|
bumpDayStamp?: string | undefined;
|
|
628
|
+
bumpMonthStamp?: string | undefined;
|
|
250
629
|
tokensToday?: number | undefined;
|
|
630
|
+
monthStamp?: string | undefined;
|
|
631
|
+
tokensThisMonth?: number | undefined;
|
|
251
632
|
reservedTodayNanos?: number | undefined;
|
|
633
|
+
reservedMonthNanos?: number | undefined;
|
|
252
634
|
reservedTotalNanos?: number | undefined;
|
|
253
635
|
reservedTodayTokens?: number | undefined;
|
|
636
|
+
reservedMonthTokens?: number | undefined;
|
|
254
637
|
reservedTotalTokens?: number | undefined;
|
|
255
638
|
pendingCount?: number | undefined;
|
|
256
|
-
|
|
639
|
+
dimension: string;
|
|
640
|
+
value: string;
|
|
257
641
|
totalSpendNanos: number;
|
|
258
642
|
totalRequests: number;
|
|
259
643
|
totalTokens: number;
|
|
260
644
|
dayStamp: string;
|
|
261
|
-
name: string;
|
|
262
645
|
}[]>;
|
|
646
|
+
/** One bucket's limits + spend (null if it has none yet). */
|
|
647
|
+
get: (ctx: RunQueryCtx, args: {
|
|
648
|
+
name: string;
|
|
649
|
+
}) => Promise<{
|
|
650
|
+
spendTodayNanos: number;
|
|
651
|
+
spendThisMonthNanos: number;
|
|
652
|
+
_id: string;
|
|
653
|
+
_creationTime: number;
|
|
654
|
+
requestsPerMinute?: number | undefined;
|
|
655
|
+
maxConcurrent?: number | undefined;
|
|
656
|
+
dailySpendLimitNanos?: number | undefined;
|
|
657
|
+
monthlySpendLimitNanos?: number | undefined;
|
|
658
|
+
lifetimeSpendLimitNanos?: number | undefined;
|
|
659
|
+
dailyTokenLimit?: number | undefined;
|
|
660
|
+
monthlyTokenLimit?: number | undefined;
|
|
661
|
+
lifetimeTokenLimit?: number | undefined;
|
|
662
|
+
blocked?: boolean | undefined;
|
|
663
|
+
warnAtPct?: number | undefined;
|
|
664
|
+
enforcement?: "hard" | "soft" | undefined;
|
|
665
|
+
dailyBumpNanos?: number | undefined;
|
|
666
|
+
monthlyBumpNanos?: number | undefined;
|
|
667
|
+
lifetimeBumpNanos?: number | undefined;
|
|
668
|
+
bumpDayStamp?: string | undefined;
|
|
669
|
+
bumpMonthStamp?: string | undefined;
|
|
670
|
+
tokensToday?: number | undefined;
|
|
671
|
+
monthStamp?: string | undefined;
|
|
672
|
+
tokensThisMonth?: number | undefined;
|
|
673
|
+
reservedTodayNanos?: number | undefined;
|
|
674
|
+
reservedMonthNanos?: number | undefined;
|
|
675
|
+
reservedTotalNanos?: number | undefined;
|
|
676
|
+
reservedTodayTokens?: number | undefined;
|
|
677
|
+
reservedMonthTokens?: number | undefined;
|
|
678
|
+
reservedTotalTokens?: number | undefined;
|
|
679
|
+
pendingCount?: number | undefined;
|
|
680
|
+
dimension: string;
|
|
681
|
+
value: string;
|
|
682
|
+
totalSpendNanos: number;
|
|
683
|
+
totalRequests: number;
|
|
684
|
+
totalTokens: number;
|
|
685
|
+
dayStamp: string;
|
|
686
|
+
} | null>;
|
|
263
687
|
setLimits: (ctx: RunMutationCtx, args: {
|
|
264
688
|
name: string;
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
dailyTokenLimit?: number;
|
|
268
|
-
lifetimeTokenLimit?: number;
|
|
269
|
-
enforcement?: "hard" | "soft";
|
|
270
|
-
disabled?: boolean;
|
|
271
|
-
}) => Promise<null>;
|
|
689
|
+
} & BucketLimits) => Promise<null>;
|
|
690
|
+
/** One-time "approve another $X" bump (daily/monthly reset with the window). */
|
|
272
691
|
bump: (ctx: RunMutationCtx, args: {
|
|
273
692
|
name: string;
|
|
274
|
-
|
|
275
|
-
|
|
693
|
+
} & BumpArgs) => Promise<null>;
|
|
694
|
+
/** Manually credit (negative) or debit (positive) this bucket. */
|
|
695
|
+
adjust: (ctx: RunMutationCtx, args: {
|
|
696
|
+
name: string;
|
|
697
|
+
} & {
|
|
698
|
+
deltaNanos: number;
|
|
699
|
+
tokens?: number;
|
|
700
|
+
reason?: string;
|
|
276
701
|
}) => Promise<null>;
|
|
702
|
+
/** Durable spend history for this bucket (per day or per month). */
|
|
703
|
+
history: (ctx: RunQueryCtx, args: {
|
|
704
|
+
name: string;
|
|
705
|
+
} & {
|
|
706
|
+
period?: "day" | "month";
|
|
707
|
+
limit?: number;
|
|
708
|
+
}) => Promise<{
|
|
709
|
+
_id: string;
|
|
710
|
+
_creationTime: number;
|
|
711
|
+
dimension: string;
|
|
712
|
+
value: string;
|
|
713
|
+
period: "day" | "month";
|
|
714
|
+
stamp: string;
|
|
715
|
+
spendNanos: number;
|
|
716
|
+
tokens: number;
|
|
717
|
+
requests: number;
|
|
718
|
+
}[]>;
|
|
719
|
+
/** Manual-adjustment audit log for this bucket. */
|
|
720
|
+
adjustments: (ctx: RunQueryCtx, args: {
|
|
721
|
+
name: string;
|
|
722
|
+
} & {
|
|
723
|
+
limit?: number;
|
|
724
|
+
}) => Promise<{
|
|
725
|
+
_id: string;
|
|
726
|
+
_creationTime: number;
|
|
727
|
+
tokens?: number | undefined;
|
|
728
|
+
reason?: string | undefined;
|
|
729
|
+
dimension: string;
|
|
730
|
+
value: string;
|
|
731
|
+
deltaNanos: number;
|
|
732
|
+
}[]>;
|
|
733
|
+
/** Delete the bucket (for "user", also its request rows). */
|
|
734
|
+
delete: (ctx: RunMutationCtx, args: {
|
|
735
|
+
name: string;
|
|
736
|
+
}) => Promise<{
|
|
737
|
+
deletedThisBatch: number;
|
|
738
|
+
done: boolean;
|
|
739
|
+
}>;
|
|
277
740
|
};
|
|
278
|
-
/** The deployment-wide budget and retention config. */
|
|
741
|
+
/** The deployment-wide budget, alerts, and retention config. */
|
|
279
742
|
get global(): {
|
|
280
743
|
/** Limits + spend today/total. */
|
|
281
744
|
status: (ctx: RunQueryCtx) => Promise<{
|
|
@@ -284,6 +747,8 @@ export declare class AIBudget {
|
|
|
284
747
|
enforcement: "hard" | "soft";
|
|
285
748
|
spentTodayNanos: number;
|
|
286
749
|
spentTotalNanos: number;
|
|
750
|
+
retentionMs: number | null;
|
|
751
|
+
defaultWarnAtPct: number | null;
|
|
287
752
|
}>;
|
|
288
753
|
/** A killswitch spend cap across all users/actions (enforced approximately). */
|
|
289
754
|
setLimits: (ctx: RunMutationCtx, args: {
|
|
@@ -295,6 +760,10 @@ export declare class AIBudget {
|
|
|
295
760
|
dailyNanos?: number;
|
|
296
761
|
lifetimeNanos?: number;
|
|
297
762
|
}) => Promise<null>;
|
|
763
|
+
/** Default approaching-limit alert threshold (fraction of a cap, e.g. 0.8). */
|
|
764
|
+
setAlertDefaults: (ctx: RunMutationCtx, args: {
|
|
765
|
+
warnAtPct?: number;
|
|
766
|
+
}) => Promise<null>;
|
|
298
767
|
/** Request-row retention window in ms (default 1h; 0 disables). */
|
|
299
768
|
setRetention: (ctx: RunMutationCtx, args: {
|
|
300
769
|
retentionMs: number;
|
|
@@ -312,12 +781,13 @@ export declare class AIBudget {
|
|
|
312
781
|
models: string[];
|
|
313
782
|
}) => Promise<null>;
|
|
314
783
|
};
|
|
315
|
-
/** Per-model prices (
|
|
784
|
+
/** Per-model prices (nanodollars per million tokens) + server-tool fees. */
|
|
316
785
|
get prices(): {
|
|
317
786
|
list: (ctx: RunQueryCtx) => Promise<{
|
|
318
787
|
[x: string]: {
|
|
319
788
|
input: number;
|
|
320
789
|
output: number;
|
|
790
|
+
cached?: number | undefined;
|
|
321
791
|
overridden: boolean;
|
|
322
792
|
};
|
|
323
793
|
}>;
|
|
@@ -325,8 +795,45 @@ export declare class AIBudget {
|
|
|
325
795
|
model: string;
|
|
326
796
|
inputNanosPerMTok: number;
|
|
327
797
|
outputNanosPerMTok: number;
|
|
798
|
+
/** Cache-read rate; defaults to a discount off input if omitted. */
|
|
799
|
+
cachedNanosPerMTok?: number;
|
|
800
|
+
}) => Promise<null>;
|
|
801
|
+
/** Per-call fees for provider server tools (web search, etc.). */
|
|
802
|
+
listServerTools: (ctx: RunQueryCtx) => Promise<{
|
|
803
|
+
[x: string]: number;
|
|
804
|
+
}>;
|
|
805
|
+
/** Set a server-tool's per-call price, e.g. { tool: "web_search", nanosPerCall }. */
|
|
806
|
+
setServerTool: (ctx: RunMutationCtx, args: {
|
|
807
|
+
tool: string;
|
|
808
|
+
nanosPerCall: number;
|
|
328
809
|
}) => Promise<null>;
|
|
329
810
|
};
|
|
811
|
+
/**
|
|
812
|
+
* Mount the built-in admin dashboard on your app's HTTP router with one call.
|
|
813
|
+
* Serves a self-contained HTML dashboard (buckets, requests, usage history,
|
|
814
|
+
* settings) plus a small JSON API, all backed by the component — no extra
|
|
815
|
+
* queries to write.
|
|
816
|
+
*
|
|
817
|
+
* // convex/http.ts
|
|
818
|
+
* import { httpRouter } from "convex/server";
|
|
819
|
+
* const http = httpRouter();
|
|
820
|
+
* ai.registerRoutes(http, { authorize: async (ctx) =>
|
|
821
|
+
* (await ctx.auth.getUserIdentity())?.role === "admin" });
|
|
822
|
+
* export default http;
|
|
823
|
+
*
|
|
824
|
+
* It then lives at `https://<deployment>.convex.site/aibudget`.
|
|
825
|
+
*
|
|
826
|
+
* SECURITY: the endpoint is public on the internet. You MUST gate it — either
|
|
827
|
+
* pass `authorize` (recommended: check the caller is a deployment admin) or
|
|
828
|
+
* set the `AI_BUDGET_DASHBOARD_TOKEN` env var (a bearer token / `?token=`).
|
|
829
|
+
* With neither, every route returns 401.
|
|
830
|
+
*/
|
|
831
|
+
registerRoutes(http: HttpRouter, opts?: {
|
|
832
|
+
/** Mount path (default "/aibudget"). */
|
|
833
|
+
path?: string;
|
|
834
|
+
/** Return true to allow the request. Runs on the HTML page and every API call. */
|
|
835
|
+
authorize?: (ctx: any, request: Request) => boolean | Promise<boolean>;
|
|
836
|
+
}): void;
|
|
330
837
|
}
|
|
331
838
|
/** @deprecated Renamed to `AIBudget`. */
|
|
332
839
|
export declare const WorryFreeAI: typeof AIBudget;
|