@convex-dev/ai-budget 0.0.2-alpha.4 → 0.0.2-alpha.5
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 +67 -6
- package/dist/client/index.d.ts +318 -43
- package/dist/client/index.js +115 -68
- package/dist/component/_generated/component.d.ts +32 -0
- package/dist/component/lib.d.ts +73 -0
- package/dist/component/lib.js +304 -32
- package/dist/component/schema.d.ts +76 -3
- package/dist/component/schema.js +56 -2
- package/package.json +1 -1
- package/src/client/index.ts +199 -131
- package/src/component/_generated/component.ts +54 -3
- package/src/component/lib.test.ts +118 -0
- package/src/component/lib.ts +375 -48
- package/src/component/schema.ts +59 -2
|
@@ -15,19 +15,30 @@ export declare const vTag: import("convex/values").VObject<{
|
|
|
15
15
|
declare const _default: import("convex/server").SchemaDefinition<{
|
|
16
16
|
buckets: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
17
17
|
requestsPerMinute?: number | undefined;
|
|
18
|
+
maxConcurrent?: number | undefined;
|
|
18
19
|
dailySpendLimitNanos?: number | undefined;
|
|
20
|
+
monthlySpendLimitNanos?: number | undefined;
|
|
19
21
|
lifetimeSpendLimitNanos?: number | undefined;
|
|
20
22
|
dailyTokenLimit?: number | undefined;
|
|
23
|
+
monthlyTokenLimit?: number | undefined;
|
|
21
24
|
lifetimeTokenLimit?: number | undefined;
|
|
22
25
|
blocked?: boolean | undefined;
|
|
26
|
+
warnAtPct?: number | undefined;
|
|
23
27
|
enforcement?: "hard" | "soft" | undefined;
|
|
24
28
|
dailyBumpNanos?: number | undefined;
|
|
29
|
+
monthlyBumpNanos?: number | undefined;
|
|
25
30
|
lifetimeBumpNanos?: number | undefined;
|
|
26
31
|
bumpDayStamp?: string | undefined;
|
|
32
|
+
bumpMonthStamp?: string | undefined;
|
|
27
33
|
tokensToday?: number | undefined;
|
|
34
|
+
monthStamp?: string | undefined;
|
|
35
|
+
spendThisMonthNanos?: number | undefined;
|
|
36
|
+
tokensThisMonth?: number | undefined;
|
|
28
37
|
reservedTodayNanos?: number | undefined;
|
|
38
|
+
reservedMonthNanos?: number | undefined;
|
|
29
39
|
reservedTotalNanos?: number | undefined;
|
|
30
40
|
reservedTodayTokens?: number | undefined;
|
|
41
|
+
reservedMonthTokens?: number | undefined;
|
|
31
42
|
reservedTotalTokens?: number | undefined;
|
|
32
43
|
pendingCount?: number | undefined;
|
|
33
44
|
dimension: string;
|
|
@@ -41,30 +52,88 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
41
52
|
dimension: import("convex/values").VString<string, "required">;
|
|
42
53
|
value: import("convex/values").VString<string, "required">;
|
|
43
54
|
requestsPerMinute: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
55
|
+
maxConcurrent: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
44
56
|
dailySpendLimitNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
57
|
+
monthlySpendLimitNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
45
58
|
lifetimeSpendLimitNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
46
59
|
dailyTokenLimit: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
60
|
+
monthlyTokenLimit: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
47
61
|
lifetimeTokenLimit: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
48
62
|
blocked: import("convex/values").VBoolean<boolean | undefined, "optional">;
|
|
63
|
+
warnAtPct: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
49
64
|
enforcement: import("convex/values").VUnion<"hard" | "soft" | undefined, [import("convex/values").VLiteral<"hard", "required">, import("convex/values").VLiteral<"soft", "required">], "optional", never>;
|
|
50
65
|
dailyBumpNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
66
|
+
monthlyBumpNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
51
67
|
lifetimeBumpNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
52
68
|
bumpDayStamp: import("convex/values").VString<string | undefined, "optional">;
|
|
69
|
+
bumpMonthStamp: import("convex/values").VString<string | undefined, "optional">;
|
|
53
70
|
totalSpendNanos: import("convex/values").VFloat64<number, "required">;
|
|
54
71
|
totalRequests: import("convex/values").VFloat64<number, "required">;
|
|
55
72
|
totalTokens: import("convex/values").VFloat64<number, "required">;
|
|
56
73
|
dayStamp: import("convex/values").VString<string, "required">;
|
|
57
74
|
spendTodayNanos: import("convex/values").VFloat64<number, "required">;
|
|
58
75
|
tokensToday: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
76
|
+
monthStamp: import("convex/values").VString<string | undefined, "optional">;
|
|
77
|
+
spendThisMonthNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
78
|
+
tokensThisMonth: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
59
79
|
reservedTodayNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
80
|
+
reservedMonthNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
60
81
|
reservedTotalNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
61
82
|
reservedTodayTokens: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
83
|
+
reservedMonthTokens: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
62
84
|
reservedTotalTokens: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
63
85
|
pendingCount: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
64
|
-
}, "required", "dimension" | "value" | "requestsPerMinute" | "dailySpendLimitNanos" | "lifetimeSpendLimitNanos" | "dailyTokenLimit" | "lifetimeTokenLimit" | "blocked" | "enforcement" | "dailyBumpNanos" | "lifetimeBumpNanos" | "bumpDayStamp" | "totalSpendNanos" | "totalRequests" | "totalTokens" | "dayStamp" | "spendTodayNanos" | "tokensToday" | "reservedTodayNanos" | "reservedTotalNanos" | "reservedTodayTokens" | "reservedTotalTokens" | "pendingCount">, {
|
|
86
|
+
}, "required", "dimension" | "value" | "requestsPerMinute" | "maxConcurrent" | "dailySpendLimitNanos" | "monthlySpendLimitNanos" | "lifetimeSpendLimitNanos" | "dailyTokenLimit" | "monthlyTokenLimit" | "lifetimeTokenLimit" | "blocked" | "warnAtPct" | "enforcement" | "dailyBumpNanos" | "monthlyBumpNanos" | "lifetimeBumpNanos" | "bumpDayStamp" | "bumpMonthStamp" | "totalSpendNanos" | "totalRequests" | "totalTokens" | "dayStamp" | "spendTodayNanos" | "tokensToday" | "monthStamp" | "spendThisMonthNanos" | "tokensThisMonth" | "reservedTodayNanos" | "reservedMonthNanos" | "reservedTotalNanos" | "reservedTodayTokens" | "reservedMonthTokens" | "reservedTotalTokens" | "pendingCount">, {
|
|
65
87
|
dim_value: ["dimension", "value", "_creationTime"];
|
|
66
88
|
dimension: ["dimension", "_creationTime"];
|
|
67
89
|
}, {}, {}>;
|
|
90
|
+
usage: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
91
|
+
dimension: string;
|
|
92
|
+
value: string;
|
|
93
|
+
period: "day" | "month";
|
|
94
|
+
stamp: string;
|
|
95
|
+
spendNanos: number;
|
|
96
|
+
tokens: number;
|
|
97
|
+
requests: number;
|
|
98
|
+
}, {
|
|
99
|
+
dimension: import("convex/values").VString<string, "required">;
|
|
100
|
+
value: import("convex/values").VString<string, "required">;
|
|
101
|
+
period: import("convex/values").VUnion<"day" | "month", [import("convex/values").VLiteral<"day", "required">, import("convex/values").VLiteral<"month", "required">], "required", never>;
|
|
102
|
+
stamp: import("convex/values").VString<string, "required">;
|
|
103
|
+
spendNanos: import("convex/values").VFloat64<number, "required">;
|
|
104
|
+
tokens: import("convex/values").VFloat64<number, "required">;
|
|
105
|
+
requests: import("convex/values").VFloat64<number, "required">;
|
|
106
|
+
}, "required", "dimension" | "value" | "period" | "stamp" | "spendNanos" | "tokens" | "requests">, {
|
|
107
|
+
bucket_period_stamp: ["dimension", "value", "period", "stamp", "_creationTime"];
|
|
108
|
+
period_stamp: ["period", "stamp", "_creationTime"];
|
|
109
|
+
}, {}, {}>;
|
|
110
|
+
requestTags: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
111
|
+
dimension: string;
|
|
112
|
+
value: string;
|
|
113
|
+
requestId: import("convex/values").GenericId<"requests">;
|
|
114
|
+
}, {
|
|
115
|
+
dimension: import("convex/values").VString<string, "required">;
|
|
116
|
+
value: import("convex/values").VString<string, "required">;
|
|
117
|
+
requestId: import("convex/values").VId<import("convex/values").GenericId<"requests">, "required">;
|
|
118
|
+
}, "required", "dimension" | "value" | "requestId">, {
|
|
119
|
+
dim_value: ["dimension", "value", "_creationTime"];
|
|
120
|
+
requestId: ["requestId", "_creationTime"];
|
|
121
|
+
}, {}, {}>;
|
|
122
|
+
adjustments: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
123
|
+
tokens?: number | undefined;
|
|
124
|
+
reason?: string | undefined;
|
|
125
|
+
dimension: string;
|
|
126
|
+
value: string;
|
|
127
|
+
deltaNanos: number;
|
|
128
|
+
}, {
|
|
129
|
+
dimension: import("convex/values").VString<string, "required">;
|
|
130
|
+
value: import("convex/values").VString<string, "required">;
|
|
131
|
+
deltaNanos: import("convex/values").VFloat64<number, "required">;
|
|
132
|
+
tokens: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
133
|
+
reason: import("convex/values").VString<string | undefined, "optional">;
|
|
134
|
+
}, "required", "dimension" | "value" | "tokens" | "deltaNanos" | "reason">, {
|
|
135
|
+
dim_value: ["dimension", "value", "_creationTime"];
|
|
136
|
+
}, {}, {}>;
|
|
68
137
|
requests: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
69
138
|
actionName?: string | undefined;
|
|
70
139
|
tags?: {
|
|
@@ -137,6 +206,7 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
137
206
|
settled: ["settled", "_creationTime"];
|
|
138
207
|
}, {}, {}>;
|
|
139
208
|
prices: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
209
|
+
cachedNanosPerMTok?: number | undefined;
|
|
140
210
|
model: string;
|
|
141
211
|
inputNanosPerMTok: number;
|
|
142
212
|
outputNanosPerMTok: number;
|
|
@@ -144,7 +214,8 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
144
214
|
model: import("convex/values").VString<string, "required">;
|
|
145
215
|
inputNanosPerMTok: import("convex/values").VFloat64<number, "required">;
|
|
146
216
|
outputNanosPerMTok: import("convex/values").VFloat64<number, "required">;
|
|
147
|
-
|
|
217
|
+
cachedNanosPerMTok: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
218
|
+
}, "required", "model" | "inputNanosPerMTok" | "outputNanosPerMTok" | "cachedNanosPerMTok">, {
|
|
148
219
|
model: ["model", "_creationTime"];
|
|
149
220
|
}, {}, {}>;
|
|
150
221
|
settings: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
@@ -157,6 +228,7 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
157
228
|
globalLifetimeBumpNanos?: number | undefined;
|
|
158
229
|
globalBumpDayStamp?: string | undefined;
|
|
159
230
|
retentionMs?: number | undefined;
|
|
231
|
+
defaultWarnAtPct?: number | undefined;
|
|
160
232
|
key: string;
|
|
161
233
|
}, {
|
|
162
234
|
key: import("convex/values").VString<string, "required">;
|
|
@@ -169,7 +241,8 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
169
241
|
globalLifetimeBumpNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
170
242
|
globalBumpDayStamp: import("convex/values").VString<string | undefined, "optional">;
|
|
171
243
|
retentionMs: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
172
|
-
|
|
244
|
+
defaultWarnAtPct: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
245
|
+
}, "required", "key" | "modelMode" | "models" | "globalDailySpendLimitNanos" | "globalLifetimeSpendLimitNanos" | "globalEnforcement" | "globalDailyBumpNanos" | "globalLifetimeBumpNanos" | "globalBumpDayStamp" | "retentionMs" | "defaultWarnAtPct">, {
|
|
173
246
|
key: ["key", "_creationTime"];
|
|
174
247
|
}, {}, {}>;
|
|
175
248
|
}, true>;
|
package/dist/component/schema.js
CHANGED
|
@@ -17,18 +17,26 @@ export default defineSchema({
|
|
|
17
17
|
value: v.string(),
|
|
18
18
|
// limits (all optional — unlimited by default)
|
|
19
19
|
requestsPerMinute: v.optional(v.number()), // enforced on the "user" dimension
|
|
20
|
+
maxConcurrent: v.optional(v.number()), // max in-flight (pending) requests
|
|
20
21
|
dailySpendLimitNanos: v.optional(v.number()),
|
|
22
|
+
monthlySpendLimitNanos: v.optional(v.number()),
|
|
21
23
|
lifetimeSpendLimitNanos: v.optional(v.number()),
|
|
22
24
|
dailyTokenLimit: v.optional(v.number()),
|
|
25
|
+
monthlyTokenLimit: v.optional(v.number()),
|
|
23
26
|
lifetimeTokenLimit: v.optional(v.number()),
|
|
24
27
|
blocked: v.optional(v.boolean()), // hard block (was `blocked`/`disabled`)
|
|
28
|
+
// Fire an approaching-limit alert once usage crosses this fraction of a cap
|
|
29
|
+
// (e.g. 0.8 = warn at 80%). Falls back to the deployment default.
|
|
30
|
+
warnAtPct: v.optional(v.number()),
|
|
25
31
|
// "hard" (default): exceeding a budget blocks. "soft": warn but allow.
|
|
26
32
|
enforcement: v.optional(v.union(v.literal("hard"), v.literal("soft"))),
|
|
27
|
-
// one-time bumps ("approve another $X"). Daily
|
|
28
|
-
// (
|
|
33
|
+
// one-time bumps ("approve another $X"). Daily/monthly bumps are scoped to
|
|
34
|
+
// their stamp (reset with the window); lifetime bump is permanent.
|
|
29
35
|
dailyBumpNanos: v.optional(v.number()),
|
|
36
|
+
monthlyBumpNanos: v.optional(v.number()),
|
|
30
37
|
lifetimeBumpNanos: v.optional(v.number()),
|
|
31
38
|
bumpDayStamp: v.optional(v.string()),
|
|
39
|
+
bumpMonthStamp: v.optional(v.string()),
|
|
32
40
|
// settled totals (from finished requests)
|
|
33
41
|
totalSpendNanos: v.number(),
|
|
34
42
|
totalRequests: v.number(),
|
|
@@ -37,15 +45,55 @@ export default defineSchema({
|
|
|
37
45
|
dayStamp: v.string(),
|
|
38
46
|
spendTodayNanos: v.number(),
|
|
39
47
|
tokensToday: v.optional(v.number()),
|
|
48
|
+
// monthly window (UTC calendar month, e.g. "2026-09")
|
|
49
|
+
monthStamp: v.optional(v.string()),
|
|
50
|
+
spendThisMonthNanos: v.optional(v.number()),
|
|
51
|
+
tokensThisMonth: v.optional(v.number()),
|
|
40
52
|
// in-flight reservations (pessimistic holds; released on settle/expiry)
|
|
41
53
|
reservedTodayNanos: v.optional(v.number()),
|
|
54
|
+
reservedMonthNanos: v.optional(v.number()),
|
|
42
55
|
reservedTotalNanos: v.optional(v.number()),
|
|
43
56
|
reservedTodayTokens: v.optional(v.number()),
|
|
57
|
+
reservedMonthTokens: v.optional(v.number()),
|
|
44
58
|
reservedTotalTokens: v.optional(v.number()),
|
|
45
59
|
pendingCount: v.optional(v.number()),
|
|
46
60
|
})
|
|
47
61
|
.index("dim_value", ["dimension", "value"])
|
|
48
62
|
.index("dimension", ["dimension"]),
|
|
63
|
+
// Durable per-(bucket, period) spend history. Written from settled requests
|
|
64
|
+
// and manual adjustments; NEVER swept by request retention, so spend charts
|
|
65
|
+
// and "what did we spend last month" survive long after the raw request rows
|
|
66
|
+
// are purged. period is "day" ("2026-09-04") or "month" ("2026-09").
|
|
67
|
+
usage: defineTable({
|
|
68
|
+
dimension: v.string(),
|
|
69
|
+
value: v.string(),
|
|
70
|
+
period: v.union(v.literal("day"), v.literal("month")),
|
|
71
|
+
stamp: v.string(),
|
|
72
|
+
spendNanos: v.number(),
|
|
73
|
+
tokens: v.number(),
|
|
74
|
+
requests: v.number(),
|
|
75
|
+
})
|
|
76
|
+
.index("bucket_period_stamp", ["dimension", "value", "period", "stamp"])
|
|
77
|
+
.index("period_stamp", ["period", "stamp"]),
|
|
78
|
+
// Reverse index for filtering the request log by an arbitrary tag dimension
|
|
79
|
+
// (user/action are already indexed on `requests`). One row per extra tag per
|
|
80
|
+
// request; cleaned up with the request on retention/deletion.
|
|
81
|
+
requestTags: defineTable({
|
|
82
|
+
dimension: v.string(),
|
|
83
|
+
value: v.string(),
|
|
84
|
+
requestId: v.id("requests"),
|
|
85
|
+
})
|
|
86
|
+
.index("dim_value", ["dimension", "value"])
|
|
87
|
+
.index("requestId", ["requestId"]),
|
|
88
|
+
// Manual credits/debits applied to a bucket (comp a user, correct an
|
|
89
|
+
// overcharge). Negative delta = credit/refund, positive = extra charge.
|
|
90
|
+
adjustments: defineTable({
|
|
91
|
+
dimension: v.string(),
|
|
92
|
+
value: v.string(),
|
|
93
|
+
deltaNanos: v.number(),
|
|
94
|
+
tokens: v.optional(v.number()),
|
|
95
|
+
reason: v.optional(v.string()),
|
|
96
|
+
}).index("dim_value", ["dimension", "value"]),
|
|
49
97
|
requests: defineTable({
|
|
50
98
|
// `user` and `action` stay first-class + indexed (the hot-path filters and
|
|
51
99
|
// rate limiting); the full attribution incl. extra tags lives in `tags`.
|
|
@@ -86,6 +134,9 @@ export default defineSchema({
|
|
|
86
134
|
model: v.string(),
|
|
87
135
|
inputNanosPerMTok: v.number(),
|
|
88
136
|
outputNanosPerMTok: v.number(),
|
|
137
|
+
// price for cached (prompt-cache-read) input tokens. Providers bill these
|
|
138
|
+
// at a fraction of the input rate; if unset, a default discount is applied.
|
|
139
|
+
cachedNanosPerMTok: v.optional(v.number()),
|
|
89
140
|
}).index("model", ["model"]),
|
|
90
141
|
// singleton component config (key === "singleton")
|
|
91
142
|
settings: defineTable({
|
|
@@ -108,5 +159,8 @@ export default defineSchema({
|
|
|
108
159
|
globalBumpDayStamp: v.optional(v.string()),
|
|
109
160
|
// request-row retention window in ms (default 1h); 0 disables sweeping.
|
|
110
161
|
retentionMs: v.optional(v.number()),
|
|
162
|
+
// default approaching-limit alert threshold (fraction of a cap) for buckets
|
|
163
|
+
// that don't set their own warnAtPct. 0/unset disables threshold alerts.
|
|
164
|
+
defaultWarnAtPct: v.optional(v.number()),
|
|
111
165
|
}).index("key", ["key"]),
|
|
112
166
|
});
|