@convex-dev/ai-budget 0.0.2-alpha.0 → 0.0.2-alpha.10
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 +335 -183
- package/dist/client/dashboard.d.ts +1 -0
- package/dist/client/dashboard.js +223 -0
- package/dist/client/index.d.ts +494 -35
- package/dist/client/index.js +276 -33
- package/dist/component/_generated/component.d.ts +56 -24
- package/dist/component/lib.d.ts +132 -40
- package/dist/component/lib.js +590 -305
- package/dist/component/schema.d.ts +103 -53
- package/dist/component/schema.js +81 -38
- package/package.json +5 -5
- package/src/client/dashboard.ts +223 -0
- package/src/client/index.ts +407 -73
- package/src/component/_generated/component.ts +79 -29
- package/src/component/lib.test.ts +208 -15
- package/src/component/lib.ts +688 -333
- package/src/component/schema.ts +84 -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,13 +79,39 @@ 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;
|
|
66
115
|
/**
|
|
67
116
|
* One-shot chat through the AI Gateway with tracking + limits.
|
|
68
117
|
* Call from an action. `userId` defaults to the authenticated caller.
|
|
@@ -76,6 +125,8 @@ export declare class AIBudget {
|
|
|
76
125
|
rerunOf?: string;
|
|
77
126
|
/** Attribute spend to this action name. Defaults to the calling Convex action. */
|
|
78
127
|
action?: string;
|
|
128
|
+
/** Extra attribution dimensions to bill/limit (team, customer, env, …). */
|
|
129
|
+
tags?: Tag[];
|
|
79
130
|
}): Promise<ChatResult>;
|
|
80
131
|
/**
|
|
81
132
|
* An AI SDK LanguageModel that enforces limits and records usage/cost for
|
|
@@ -87,17 +138,26 @@ export declare class AIBudget {
|
|
|
87
138
|
userId?: string;
|
|
88
139
|
model?: string;
|
|
89
140
|
action?: string;
|
|
141
|
+
/** Extra attribution dimensions to bill/limit (team, customer, env, …). */
|
|
142
|
+
tags?: Tag[];
|
|
90
143
|
}): LanguageModel;
|
|
91
144
|
private rerunImpl;
|
|
92
145
|
/** The request audit log, replay, and re-run lineage. */
|
|
93
146
|
get requests(): {
|
|
147
|
+
/** Filter by userId, or by any {dimension, value} (incl. custom tags). */
|
|
94
148
|
list: (ctx: RunQueryCtx, args?: {
|
|
95
149
|
userId?: string;
|
|
150
|
+
dimension?: string;
|
|
151
|
+
value?: string;
|
|
96
152
|
limit?: number;
|
|
97
153
|
}) => Promise<{
|
|
98
154
|
_id: string;
|
|
99
155
|
_creationTime: number;
|
|
100
156
|
actionName?: string | undefined;
|
|
157
|
+
tags?: {
|
|
158
|
+
dimension: string;
|
|
159
|
+
value: string;
|
|
160
|
+
}[] | undefined;
|
|
101
161
|
estimatedNanos?: number | undefined;
|
|
102
162
|
estimatedTokens?: number | undefined;
|
|
103
163
|
unpricedModel?: boolean | undefined;
|
|
@@ -119,6 +179,38 @@ export declare class AIBudget {
|
|
|
119
179
|
}[];
|
|
120
180
|
status: "blocked" | "pending" | "success" | "error";
|
|
121
181
|
}[]>;
|
|
182
|
+
/** One request, including its stored prompt and response. */
|
|
183
|
+
get: (ctx: RunQueryCtx, args: {
|
|
184
|
+
requestId: string;
|
|
185
|
+
}) => Promise<{
|
|
186
|
+
_id: string;
|
|
187
|
+
_creationTime: number;
|
|
188
|
+
actionName?: string | undefined;
|
|
189
|
+
tags?: {
|
|
190
|
+
dimension: string;
|
|
191
|
+
value: string;
|
|
192
|
+
}[] | undefined;
|
|
193
|
+
estimatedNanos?: number | undefined;
|
|
194
|
+
estimatedTokens?: number | undefined;
|
|
195
|
+
unpricedModel?: boolean | undefined;
|
|
196
|
+
overBudget?: boolean | undefined;
|
|
197
|
+
settled?: boolean | undefined;
|
|
198
|
+
error?: string | undefined;
|
|
199
|
+
responseText?: string | undefined;
|
|
200
|
+
promptTokens?: number | undefined;
|
|
201
|
+
completionTokens?: number | undefined;
|
|
202
|
+
cachedTokens?: number | undefined;
|
|
203
|
+
costNanos?: number | undefined;
|
|
204
|
+
latencyMs?: number | undefined;
|
|
205
|
+
rerunOf?: string | undefined;
|
|
206
|
+
userId: string;
|
|
207
|
+
model: string;
|
|
208
|
+
messages: {
|
|
209
|
+
role: string;
|
|
210
|
+
content: string;
|
|
211
|
+
}[];
|
|
212
|
+
status: "blocked" | "pending" | "success" | "error";
|
|
213
|
+
} | null>;
|
|
122
214
|
/** Ancestors up to the original, plus direct re-runs. */
|
|
123
215
|
lineage: (ctx: RunQueryCtx, args: {
|
|
124
216
|
requestId: string;
|
|
@@ -127,6 +219,10 @@ export declare class AIBudget {
|
|
|
127
219
|
_id: string;
|
|
128
220
|
_creationTime: number;
|
|
129
221
|
actionName?: string | undefined;
|
|
222
|
+
tags?: {
|
|
223
|
+
dimension: string;
|
|
224
|
+
value: string;
|
|
225
|
+
}[] | undefined;
|
|
130
226
|
estimatedNanos?: number | undefined;
|
|
131
227
|
estimatedTokens?: number | undefined;
|
|
132
228
|
unpricedModel?: boolean | undefined;
|
|
@@ -152,6 +248,10 @@ export declare class AIBudget {
|
|
|
152
248
|
_id: string;
|
|
153
249
|
_creationTime: number;
|
|
154
250
|
actionName?: string | undefined;
|
|
251
|
+
tags?: {
|
|
252
|
+
dimension: string;
|
|
253
|
+
value: string;
|
|
254
|
+
}[] | undefined;
|
|
155
255
|
estimatedNanos?: number | undefined;
|
|
156
256
|
estimatedTokens?: number | undefined;
|
|
157
257
|
unpricedModel?: boolean | undefined;
|
|
@@ -181,51 +281,281 @@ export declare class AIBudget {
|
|
|
181
281
|
model?: string;
|
|
182
282
|
}) => Promise<ChatResult>;
|
|
183
283
|
};
|
|
184
|
-
/**
|
|
284
|
+
/**
|
|
285
|
+
* Budgets and controls for an arbitrary attribution dimension — the
|
|
286
|
+
* generalization of `users`/`actions`. Give it any dimension name (team,
|
|
287
|
+
* project, tenant, customer, env, feature, …) and set caps per value:
|
|
288
|
+
*
|
|
289
|
+
* ai.tag("customer").setLimits(ctx, { value: "acme", monthlySpendLimitNanos });
|
|
290
|
+
* ai.tag("customer").history(ctx, { value: "acme", period: "day" });
|
|
291
|
+
*
|
|
292
|
+
* Attribute a call to it by passing `tags` to `chat`/`languageModel`.
|
|
293
|
+
*/
|
|
294
|
+
tag(dimension: string): {
|
|
295
|
+
/** All buckets in this dimension. */
|
|
296
|
+
list: (ctx: RunQueryCtx) => Promise<{
|
|
297
|
+
spendTodayNanos: number;
|
|
298
|
+
spendThisMonthNanos: number;
|
|
299
|
+
_id: string;
|
|
300
|
+
_creationTime: number;
|
|
301
|
+
requestsPerMinute?: number | undefined;
|
|
302
|
+
maxConcurrent?: number | undefined;
|
|
303
|
+
dailySpendLimitNanos?: number | undefined;
|
|
304
|
+
monthlySpendLimitNanos?: number | undefined;
|
|
305
|
+
lifetimeSpendLimitNanos?: number | undefined;
|
|
306
|
+
dailyTokenLimit?: number | undefined;
|
|
307
|
+
monthlyTokenLimit?: number | undefined;
|
|
308
|
+
lifetimeTokenLimit?: number | undefined;
|
|
309
|
+
blocked?: boolean | undefined;
|
|
310
|
+
warnAtPct?: number | undefined;
|
|
311
|
+
enforcement?: "hard" | "soft" | undefined;
|
|
312
|
+
dailyBumpNanos?: number | undefined;
|
|
313
|
+
monthlyBumpNanos?: number | undefined;
|
|
314
|
+
lifetimeBumpNanos?: number | undefined;
|
|
315
|
+
bumpDayStamp?: string | undefined;
|
|
316
|
+
bumpMonthStamp?: string | undefined;
|
|
317
|
+
tokensToday?: number | undefined;
|
|
318
|
+
monthStamp?: string | undefined;
|
|
319
|
+
tokensThisMonth?: number | undefined;
|
|
320
|
+
reservedTodayNanos?: number | undefined;
|
|
321
|
+
reservedMonthNanos?: number | undefined;
|
|
322
|
+
reservedTotalNanos?: number | undefined;
|
|
323
|
+
reservedTodayTokens?: number | undefined;
|
|
324
|
+
reservedMonthTokens?: number | undefined;
|
|
325
|
+
reservedTotalTokens?: number | undefined;
|
|
326
|
+
pendingCount?: number | undefined;
|
|
327
|
+
dimension: string;
|
|
328
|
+
value: string;
|
|
329
|
+
totalSpendNanos: number;
|
|
330
|
+
totalRequests: number;
|
|
331
|
+
totalTokens: number;
|
|
332
|
+
dayStamp: string;
|
|
333
|
+
}[]>;
|
|
334
|
+
/** One bucket's limits + spend (null if it has none yet). */
|
|
335
|
+
get: (ctx: RunQueryCtx, args: {
|
|
336
|
+
value: string;
|
|
337
|
+
}) => Promise<{
|
|
338
|
+
spendTodayNanos: number;
|
|
339
|
+
spendThisMonthNanos: number;
|
|
340
|
+
_id: string;
|
|
341
|
+
_creationTime: number;
|
|
342
|
+
requestsPerMinute?: number | undefined;
|
|
343
|
+
maxConcurrent?: number | undefined;
|
|
344
|
+
dailySpendLimitNanos?: number | undefined;
|
|
345
|
+
monthlySpendLimitNanos?: number | undefined;
|
|
346
|
+
lifetimeSpendLimitNanos?: number | undefined;
|
|
347
|
+
dailyTokenLimit?: number | undefined;
|
|
348
|
+
monthlyTokenLimit?: number | undefined;
|
|
349
|
+
lifetimeTokenLimit?: number | undefined;
|
|
350
|
+
blocked?: boolean | undefined;
|
|
351
|
+
warnAtPct?: number | undefined;
|
|
352
|
+
enforcement?: "hard" | "soft" | undefined;
|
|
353
|
+
dailyBumpNanos?: number | undefined;
|
|
354
|
+
monthlyBumpNanos?: number | undefined;
|
|
355
|
+
lifetimeBumpNanos?: number | undefined;
|
|
356
|
+
bumpDayStamp?: string | undefined;
|
|
357
|
+
bumpMonthStamp?: string | undefined;
|
|
358
|
+
tokensToday?: number | undefined;
|
|
359
|
+
monthStamp?: string | undefined;
|
|
360
|
+
tokensThisMonth?: number | undefined;
|
|
361
|
+
reservedTodayNanos?: number | undefined;
|
|
362
|
+
reservedMonthNanos?: number | undefined;
|
|
363
|
+
reservedTotalNanos?: number | undefined;
|
|
364
|
+
reservedTodayTokens?: number | undefined;
|
|
365
|
+
reservedMonthTokens?: number | undefined;
|
|
366
|
+
reservedTotalTokens?: number | undefined;
|
|
367
|
+
pendingCount?: number | undefined;
|
|
368
|
+
dimension: string;
|
|
369
|
+
value: string;
|
|
370
|
+
totalSpendNanos: number;
|
|
371
|
+
totalRequests: number;
|
|
372
|
+
totalTokens: number;
|
|
373
|
+
dayStamp: string;
|
|
374
|
+
} | null>;
|
|
375
|
+
setLimits: (ctx: RunMutationCtx, args: {
|
|
376
|
+
value: string;
|
|
377
|
+
} & BucketLimits) => Promise<null>;
|
|
378
|
+
/** One-time "approve another $X" bump (daily/monthly reset with the window). */
|
|
379
|
+
bump: (ctx: RunMutationCtx, args: {
|
|
380
|
+
value: string;
|
|
381
|
+
} & BumpArgs) => Promise<null>;
|
|
382
|
+
/** Manually credit (negative) or debit (positive) this bucket. */
|
|
383
|
+
adjust: (ctx: RunMutationCtx, args: {
|
|
384
|
+
value: string;
|
|
385
|
+
} & {
|
|
386
|
+
deltaNanos: number;
|
|
387
|
+
tokens?: number;
|
|
388
|
+
reason?: string;
|
|
389
|
+
}) => Promise<null>;
|
|
390
|
+
/** Durable spend history for this bucket (per day or per month). */
|
|
391
|
+
history: (ctx: RunQueryCtx, args: {
|
|
392
|
+
value: string;
|
|
393
|
+
} & {
|
|
394
|
+
period?: "day" | "month";
|
|
395
|
+
limit?: number;
|
|
396
|
+
}) => Promise<{
|
|
397
|
+
_id: string;
|
|
398
|
+
_creationTime: number;
|
|
399
|
+
dimension: string;
|
|
400
|
+
value: string;
|
|
401
|
+
period: "day" | "month";
|
|
402
|
+
stamp: string;
|
|
403
|
+
spendNanos: number;
|
|
404
|
+
tokens: number;
|
|
405
|
+
requests: number;
|
|
406
|
+
}[]>;
|
|
407
|
+
/** Manual-adjustment audit log for this bucket. */
|
|
408
|
+
adjustments: (ctx: RunQueryCtx, args: {
|
|
409
|
+
value: string;
|
|
410
|
+
} & {
|
|
411
|
+
limit?: number;
|
|
412
|
+
}) => Promise<{
|
|
413
|
+
_id: string;
|
|
414
|
+
_creationTime: number;
|
|
415
|
+
tokens?: number | undefined;
|
|
416
|
+
reason?: string | undefined;
|
|
417
|
+
dimension: string;
|
|
418
|
+
value: string;
|
|
419
|
+
deltaNanos: number;
|
|
420
|
+
}[]>;
|
|
421
|
+
/** Delete the bucket (for "user", also its request rows). */
|
|
422
|
+
delete: (ctx: RunMutationCtx, args: {
|
|
423
|
+
value: string;
|
|
424
|
+
}) => Promise<{
|
|
425
|
+
deletedThisBatch: number;
|
|
426
|
+
done: boolean;
|
|
427
|
+
}>;
|
|
428
|
+
};
|
|
429
|
+
private dimensionApi;
|
|
430
|
+
/** Per-user budgets and controls — sugar over the "user" dimension. */
|
|
185
431
|
get users(): {
|
|
432
|
+
/** All buckets in this dimension. */
|
|
186
433
|
list: (ctx: RunQueryCtx) => Promise<{
|
|
187
434
|
spendTodayNanos: number;
|
|
435
|
+
spendThisMonthNanos: number;
|
|
188
436
|
_id: string;
|
|
189
437
|
_creationTime: number;
|
|
190
438
|
requestsPerMinute?: number | undefined;
|
|
439
|
+
maxConcurrent?: number | undefined;
|
|
191
440
|
dailySpendLimitNanos?: number | undefined;
|
|
441
|
+
monthlySpendLimitNanos?: number | undefined;
|
|
192
442
|
lifetimeSpendLimitNanos?: number | undefined;
|
|
193
443
|
dailyTokenLimit?: number | undefined;
|
|
444
|
+
monthlyTokenLimit?: number | undefined;
|
|
194
445
|
lifetimeTokenLimit?: number | undefined;
|
|
195
446
|
blocked?: boolean | undefined;
|
|
447
|
+
warnAtPct?: number | undefined;
|
|
196
448
|
enforcement?: "hard" | "soft" | undefined;
|
|
197
449
|
dailyBumpNanos?: number | undefined;
|
|
450
|
+
monthlyBumpNanos?: number | undefined;
|
|
198
451
|
lifetimeBumpNanos?: number | undefined;
|
|
199
452
|
bumpDayStamp?: string | undefined;
|
|
453
|
+
bumpMonthStamp?: string | undefined;
|
|
200
454
|
tokensToday?: number | undefined;
|
|
455
|
+
monthStamp?: string | undefined;
|
|
456
|
+
tokensThisMonth?: number | undefined;
|
|
201
457
|
reservedTodayNanos?: number | undefined;
|
|
458
|
+
reservedMonthNanos?: number | undefined;
|
|
202
459
|
reservedTotalNanos?: number | undefined;
|
|
203
460
|
reservedTodayTokens?: number | undefined;
|
|
461
|
+
reservedMonthTokens?: number | undefined;
|
|
204
462
|
reservedTotalTokens?: number | undefined;
|
|
205
463
|
pendingCount?: number | undefined;
|
|
206
|
-
|
|
464
|
+
dimension: string;
|
|
465
|
+
value: string;
|
|
207
466
|
totalSpendNanos: number;
|
|
208
467
|
totalRequests: number;
|
|
209
468
|
totalTokens: number;
|
|
210
469
|
dayStamp: string;
|
|
211
470
|
}[]>;
|
|
471
|
+
/** One bucket's limits + spend (null if it has none yet). */
|
|
472
|
+
get: (ctx: RunQueryCtx, args: {
|
|
473
|
+
userId: string;
|
|
474
|
+
}) => Promise<{
|
|
475
|
+
spendTodayNanos: number;
|
|
476
|
+
spendThisMonthNanos: number;
|
|
477
|
+
_id: string;
|
|
478
|
+
_creationTime: number;
|
|
479
|
+
requestsPerMinute?: number | undefined;
|
|
480
|
+
maxConcurrent?: number | undefined;
|
|
481
|
+
dailySpendLimitNanos?: number | undefined;
|
|
482
|
+
monthlySpendLimitNanos?: number | undefined;
|
|
483
|
+
lifetimeSpendLimitNanos?: number | undefined;
|
|
484
|
+
dailyTokenLimit?: number | undefined;
|
|
485
|
+
monthlyTokenLimit?: number | undefined;
|
|
486
|
+
lifetimeTokenLimit?: number | undefined;
|
|
487
|
+
blocked?: boolean | undefined;
|
|
488
|
+
warnAtPct?: number | undefined;
|
|
489
|
+
enforcement?: "hard" | "soft" | undefined;
|
|
490
|
+
dailyBumpNanos?: number | undefined;
|
|
491
|
+
monthlyBumpNanos?: number | undefined;
|
|
492
|
+
lifetimeBumpNanos?: number | undefined;
|
|
493
|
+
bumpDayStamp?: string | undefined;
|
|
494
|
+
bumpMonthStamp?: string | undefined;
|
|
495
|
+
tokensToday?: number | undefined;
|
|
496
|
+
monthStamp?: string | undefined;
|
|
497
|
+
tokensThisMonth?: number | undefined;
|
|
498
|
+
reservedTodayNanos?: number | undefined;
|
|
499
|
+
reservedMonthNanos?: number | undefined;
|
|
500
|
+
reservedTotalNanos?: number | undefined;
|
|
501
|
+
reservedTodayTokens?: number | undefined;
|
|
502
|
+
reservedMonthTokens?: number | undefined;
|
|
503
|
+
reservedTotalTokens?: number | undefined;
|
|
504
|
+
pendingCount?: number | undefined;
|
|
505
|
+
dimension: string;
|
|
506
|
+
value: string;
|
|
507
|
+
totalSpendNanos: number;
|
|
508
|
+
totalRequests: number;
|
|
509
|
+
totalTokens: number;
|
|
510
|
+
dayStamp: string;
|
|
511
|
+
} | null>;
|
|
212
512
|
setLimits: (ctx: RunMutationCtx, args: {
|
|
213
513
|
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). */
|
|
514
|
+
} & BucketLimits) => Promise<null>;
|
|
515
|
+
/** One-time "approve another $X" bump (daily/monthly reset with the window). */
|
|
223
516
|
bump: (ctx: RunMutationCtx, args: {
|
|
224
517
|
userId: string;
|
|
225
|
-
|
|
226
|
-
|
|
518
|
+
} & BumpArgs) => Promise<null>;
|
|
519
|
+
/** Manually credit (negative) or debit (positive) this bucket. */
|
|
520
|
+
adjust: (ctx: RunMutationCtx, args: {
|
|
521
|
+
userId: string;
|
|
522
|
+
} & {
|
|
523
|
+
deltaNanos: number;
|
|
524
|
+
tokens?: number;
|
|
525
|
+
reason?: string;
|
|
227
526
|
}) => Promise<null>;
|
|
228
|
-
/**
|
|
527
|
+
/** Durable spend history for this bucket (per day or per month). */
|
|
528
|
+
history: (ctx: RunQueryCtx, args: {
|
|
529
|
+
userId: string;
|
|
530
|
+
} & {
|
|
531
|
+
period?: "day" | "month";
|
|
532
|
+
limit?: number;
|
|
533
|
+
}) => Promise<{
|
|
534
|
+
_id: string;
|
|
535
|
+
_creationTime: number;
|
|
536
|
+
dimension: string;
|
|
537
|
+
value: string;
|
|
538
|
+
period: "day" | "month";
|
|
539
|
+
stamp: string;
|
|
540
|
+
spendNanos: number;
|
|
541
|
+
tokens: number;
|
|
542
|
+
requests: number;
|
|
543
|
+
}[]>;
|
|
544
|
+
/** Manual-adjustment audit log for this bucket. */
|
|
545
|
+
adjustments: (ctx: RunQueryCtx, args: {
|
|
546
|
+
userId: string;
|
|
547
|
+
} & {
|
|
548
|
+
limit?: number;
|
|
549
|
+
}) => Promise<{
|
|
550
|
+
_id: string;
|
|
551
|
+
_creationTime: number;
|
|
552
|
+
tokens?: number | undefined;
|
|
553
|
+
reason?: string | undefined;
|
|
554
|
+
dimension: string;
|
|
555
|
+
value: string;
|
|
556
|
+
deltaNanos: number;
|
|
557
|
+
}[]>;
|
|
558
|
+
/** Delete the bucket (for "user", also its request rows). */
|
|
229
559
|
delete: (ctx: RunMutationCtx, args: {
|
|
230
560
|
userId: string;
|
|
231
561
|
}) => Promise<{
|
|
@@ -233,49 +563,143 @@ export declare class AIBudget {
|
|
|
233
563
|
done: boolean;
|
|
234
564
|
}>;
|
|
235
565
|
};
|
|
236
|
-
/** Per-action (per-feature) budgets. */
|
|
566
|
+
/** Per-action (per-feature) budgets — sugar over the "action" dimension. */
|
|
237
567
|
get actions(): {
|
|
568
|
+
/** All buckets in this dimension. */
|
|
238
569
|
list: (ctx: RunQueryCtx) => Promise<{
|
|
239
570
|
spendTodayNanos: number;
|
|
571
|
+
spendThisMonthNanos: number;
|
|
240
572
|
_id: string;
|
|
241
573
|
_creationTime: number;
|
|
574
|
+
requestsPerMinute?: number | undefined;
|
|
575
|
+
maxConcurrent?: number | undefined;
|
|
242
576
|
dailySpendLimitNanos?: number | undefined;
|
|
577
|
+
monthlySpendLimitNanos?: number | undefined;
|
|
243
578
|
lifetimeSpendLimitNanos?: number | undefined;
|
|
244
579
|
dailyTokenLimit?: number | undefined;
|
|
580
|
+
monthlyTokenLimit?: number | undefined;
|
|
245
581
|
lifetimeTokenLimit?: number | undefined;
|
|
582
|
+
blocked?: boolean | undefined;
|
|
583
|
+
warnAtPct?: number | undefined;
|
|
246
584
|
enforcement?: "hard" | "soft" | undefined;
|
|
247
585
|
dailyBumpNanos?: number | undefined;
|
|
586
|
+
monthlyBumpNanos?: number | undefined;
|
|
248
587
|
lifetimeBumpNanos?: number | undefined;
|
|
249
588
|
bumpDayStamp?: string | undefined;
|
|
589
|
+
bumpMonthStamp?: string | undefined;
|
|
250
590
|
tokensToday?: number | undefined;
|
|
591
|
+
monthStamp?: string | undefined;
|
|
592
|
+
tokensThisMonth?: number | undefined;
|
|
251
593
|
reservedTodayNanos?: number | undefined;
|
|
594
|
+
reservedMonthNanos?: number | undefined;
|
|
252
595
|
reservedTotalNanos?: number | undefined;
|
|
253
596
|
reservedTodayTokens?: number | undefined;
|
|
597
|
+
reservedMonthTokens?: number | undefined;
|
|
254
598
|
reservedTotalTokens?: number | undefined;
|
|
255
599
|
pendingCount?: number | undefined;
|
|
256
|
-
|
|
600
|
+
dimension: string;
|
|
601
|
+
value: string;
|
|
257
602
|
totalSpendNanos: number;
|
|
258
603
|
totalRequests: number;
|
|
259
604
|
totalTokens: number;
|
|
260
605
|
dayStamp: string;
|
|
261
|
-
name: string;
|
|
262
606
|
}[]>;
|
|
607
|
+
/** One bucket's limits + spend (null if it has none yet). */
|
|
608
|
+
get: (ctx: RunQueryCtx, args: {
|
|
609
|
+
name: string;
|
|
610
|
+
}) => Promise<{
|
|
611
|
+
spendTodayNanos: number;
|
|
612
|
+
spendThisMonthNanos: number;
|
|
613
|
+
_id: string;
|
|
614
|
+
_creationTime: number;
|
|
615
|
+
requestsPerMinute?: number | undefined;
|
|
616
|
+
maxConcurrent?: number | undefined;
|
|
617
|
+
dailySpendLimitNanos?: number | undefined;
|
|
618
|
+
monthlySpendLimitNanos?: number | undefined;
|
|
619
|
+
lifetimeSpendLimitNanos?: number | undefined;
|
|
620
|
+
dailyTokenLimit?: number | undefined;
|
|
621
|
+
monthlyTokenLimit?: number | undefined;
|
|
622
|
+
lifetimeTokenLimit?: number | undefined;
|
|
623
|
+
blocked?: boolean | undefined;
|
|
624
|
+
warnAtPct?: number | undefined;
|
|
625
|
+
enforcement?: "hard" | "soft" | undefined;
|
|
626
|
+
dailyBumpNanos?: number | undefined;
|
|
627
|
+
monthlyBumpNanos?: number | undefined;
|
|
628
|
+
lifetimeBumpNanos?: number | undefined;
|
|
629
|
+
bumpDayStamp?: string | undefined;
|
|
630
|
+
bumpMonthStamp?: string | undefined;
|
|
631
|
+
tokensToday?: number | undefined;
|
|
632
|
+
monthStamp?: string | undefined;
|
|
633
|
+
tokensThisMonth?: number | undefined;
|
|
634
|
+
reservedTodayNanos?: number | undefined;
|
|
635
|
+
reservedMonthNanos?: number | undefined;
|
|
636
|
+
reservedTotalNanos?: number | undefined;
|
|
637
|
+
reservedTodayTokens?: number | undefined;
|
|
638
|
+
reservedMonthTokens?: number | undefined;
|
|
639
|
+
reservedTotalTokens?: number | undefined;
|
|
640
|
+
pendingCount?: number | undefined;
|
|
641
|
+
dimension: string;
|
|
642
|
+
value: string;
|
|
643
|
+
totalSpendNanos: number;
|
|
644
|
+
totalRequests: number;
|
|
645
|
+
totalTokens: number;
|
|
646
|
+
dayStamp: string;
|
|
647
|
+
} | null>;
|
|
263
648
|
setLimits: (ctx: RunMutationCtx, args: {
|
|
264
649
|
name: string;
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
dailyTokenLimit?: number;
|
|
268
|
-
lifetimeTokenLimit?: number;
|
|
269
|
-
enforcement?: "hard" | "soft";
|
|
270
|
-
disabled?: boolean;
|
|
271
|
-
}) => Promise<null>;
|
|
650
|
+
} & BucketLimits) => Promise<null>;
|
|
651
|
+
/** One-time "approve another $X" bump (daily/monthly reset with the window). */
|
|
272
652
|
bump: (ctx: RunMutationCtx, args: {
|
|
273
653
|
name: string;
|
|
274
|
-
|
|
275
|
-
|
|
654
|
+
} & BumpArgs) => Promise<null>;
|
|
655
|
+
/** Manually credit (negative) or debit (positive) this bucket. */
|
|
656
|
+
adjust: (ctx: RunMutationCtx, args: {
|
|
657
|
+
name: string;
|
|
658
|
+
} & {
|
|
659
|
+
deltaNanos: number;
|
|
660
|
+
tokens?: number;
|
|
661
|
+
reason?: string;
|
|
276
662
|
}) => Promise<null>;
|
|
663
|
+
/** Durable spend history for this bucket (per day or per month). */
|
|
664
|
+
history: (ctx: RunQueryCtx, args: {
|
|
665
|
+
name: string;
|
|
666
|
+
} & {
|
|
667
|
+
period?: "day" | "month";
|
|
668
|
+
limit?: number;
|
|
669
|
+
}) => Promise<{
|
|
670
|
+
_id: string;
|
|
671
|
+
_creationTime: number;
|
|
672
|
+
dimension: string;
|
|
673
|
+
value: string;
|
|
674
|
+
period: "day" | "month";
|
|
675
|
+
stamp: string;
|
|
676
|
+
spendNanos: number;
|
|
677
|
+
tokens: number;
|
|
678
|
+
requests: number;
|
|
679
|
+
}[]>;
|
|
680
|
+
/** Manual-adjustment audit log for this bucket. */
|
|
681
|
+
adjustments: (ctx: RunQueryCtx, args: {
|
|
682
|
+
name: string;
|
|
683
|
+
} & {
|
|
684
|
+
limit?: number;
|
|
685
|
+
}) => Promise<{
|
|
686
|
+
_id: string;
|
|
687
|
+
_creationTime: number;
|
|
688
|
+
tokens?: number | undefined;
|
|
689
|
+
reason?: string | undefined;
|
|
690
|
+
dimension: string;
|
|
691
|
+
value: string;
|
|
692
|
+
deltaNanos: number;
|
|
693
|
+
}[]>;
|
|
694
|
+
/** Delete the bucket (for "user", also its request rows). */
|
|
695
|
+
delete: (ctx: RunMutationCtx, args: {
|
|
696
|
+
name: string;
|
|
697
|
+
}) => Promise<{
|
|
698
|
+
deletedThisBatch: number;
|
|
699
|
+
done: boolean;
|
|
700
|
+
}>;
|
|
277
701
|
};
|
|
278
|
-
/** The deployment-wide budget and retention config. */
|
|
702
|
+
/** The deployment-wide budget, alerts, and retention config. */
|
|
279
703
|
get global(): {
|
|
280
704
|
/** Limits + spend today/total. */
|
|
281
705
|
status: (ctx: RunQueryCtx) => Promise<{
|
|
@@ -284,6 +708,8 @@ export declare class AIBudget {
|
|
|
284
708
|
enforcement: "hard" | "soft";
|
|
285
709
|
spentTodayNanos: number;
|
|
286
710
|
spentTotalNanos: number;
|
|
711
|
+
retentionMs: number | null;
|
|
712
|
+
defaultWarnAtPct: number | null;
|
|
287
713
|
}>;
|
|
288
714
|
/** A killswitch spend cap across all users/actions (enforced approximately). */
|
|
289
715
|
setLimits: (ctx: RunMutationCtx, args: {
|
|
@@ -295,6 +721,10 @@ export declare class AIBudget {
|
|
|
295
721
|
dailyNanos?: number;
|
|
296
722
|
lifetimeNanos?: number;
|
|
297
723
|
}) => Promise<null>;
|
|
724
|
+
/** Default approaching-limit alert threshold (fraction of a cap, e.g. 0.8). */
|
|
725
|
+
setAlertDefaults: (ctx: RunMutationCtx, args: {
|
|
726
|
+
warnAtPct?: number;
|
|
727
|
+
}) => Promise<null>;
|
|
298
728
|
/** Request-row retention window in ms (default 1h; 0 disables). */
|
|
299
729
|
setRetention: (ctx: RunMutationCtx, args: {
|
|
300
730
|
retentionMs: number;
|
|
@@ -318,6 +748,7 @@ export declare class AIBudget {
|
|
|
318
748
|
[x: string]: {
|
|
319
749
|
input: number;
|
|
320
750
|
output: number;
|
|
751
|
+
cached?: number | undefined;
|
|
321
752
|
overridden: boolean;
|
|
322
753
|
};
|
|
323
754
|
}>;
|
|
@@ -325,8 +756,36 @@ export declare class AIBudget {
|
|
|
325
756
|
model: string;
|
|
326
757
|
inputNanosPerMTok: number;
|
|
327
758
|
outputNanosPerMTok: number;
|
|
759
|
+
/** Cache-read rate; defaults to a discount off input if omitted. */
|
|
760
|
+
cachedNanosPerMTok?: number;
|
|
328
761
|
}) => Promise<null>;
|
|
329
762
|
};
|
|
763
|
+
/**
|
|
764
|
+
* Mount the built-in admin dashboard on your app's HTTP router with one call.
|
|
765
|
+
* Serves a self-contained HTML dashboard (buckets, requests, usage history,
|
|
766
|
+
* settings) plus a small JSON API, all backed by the component — no extra
|
|
767
|
+
* queries to write.
|
|
768
|
+
*
|
|
769
|
+
* // convex/http.ts
|
|
770
|
+
* import { httpRouter } from "convex/server";
|
|
771
|
+
* const http = httpRouter();
|
|
772
|
+
* ai.registerRoutes(http, { authorize: async (ctx) =>
|
|
773
|
+
* (await ctx.auth.getUserIdentity())?.role === "admin" });
|
|
774
|
+
* export default http;
|
|
775
|
+
*
|
|
776
|
+
* It then lives at `https://<deployment>.convex.site/aibudget`.
|
|
777
|
+
*
|
|
778
|
+
* SECURITY: the endpoint is public on the internet. You MUST gate it — either
|
|
779
|
+
* pass `authorize` (recommended: check the caller is a deployment admin) or
|
|
780
|
+
* set the `AI_BUDGET_DASHBOARD_TOKEN` env var (a bearer token / `?token=`).
|
|
781
|
+
* With neither, every route returns 401.
|
|
782
|
+
*/
|
|
783
|
+
registerRoutes(http: HttpRouter, opts?: {
|
|
784
|
+
/** Mount path (default "/aibudget"). */
|
|
785
|
+
path?: string;
|
|
786
|
+
/** Return true to allow the request. Runs on the HTML page and every API call. */
|
|
787
|
+
authorize?: (ctx: any, request: Request) => boolean | Promise<boolean>;
|
|
788
|
+
}): void;
|
|
330
789
|
}
|
|
331
790
|
/** @deprecated Renamed to `AIBudget`. */
|
|
332
791
|
export declare const WorryFreeAI: typeof AIBudget;
|