@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
|
@@ -5,105 +5,141 @@ export declare const vMessage: import("convex/values").VObject<{
|
|
|
5
5
|
role: import("convex/values").VString<string, "required">;
|
|
6
6
|
content: import("convex/values").VString<string, "required">;
|
|
7
7
|
}, "required", "role" | "content">;
|
|
8
|
+
export declare const vTag: import("convex/values").VObject<{
|
|
9
|
+
dimension: string;
|
|
10
|
+
value: string;
|
|
11
|
+
}, {
|
|
12
|
+
dimension: import("convex/values").VString<string, "required">;
|
|
13
|
+
value: import("convex/values").VString<string, "required">;
|
|
14
|
+
}, "required", "dimension" | "value">;
|
|
8
15
|
declare const _default: import("convex/server").SchemaDefinition<{
|
|
9
|
-
|
|
16
|
+
buckets: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
10
17
|
requestsPerMinute?: number | undefined;
|
|
18
|
+
maxConcurrent?: number | undefined;
|
|
11
19
|
dailySpendLimitNanos?: number | undefined;
|
|
20
|
+
monthlySpendLimitNanos?: number | undefined;
|
|
12
21
|
lifetimeSpendLimitNanos?: number | undefined;
|
|
13
22
|
dailyTokenLimit?: number | undefined;
|
|
23
|
+
monthlyTokenLimit?: number | undefined;
|
|
14
24
|
lifetimeTokenLimit?: number | undefined;
|
|
15
25
|
blocked?: boolean | undefined;
|
|
26
|
+
warnAtPct?: number | undefined;
|
|
16
27
|
enforcement?: "hard" | "soft" | undefined;
|
|
17
28
|
dailyBumpNanos?: number | undefined;
|
|
29
|
+
monthlyBumpNanos?: number | undefined;
|
|
18
30
|
lifetimeBumpNanos?: number | undefined;
|
|
19
31
|
bumpDayStamp?: string | undefined;
|
|
32
|
+
bumpMonthStamp?: string | undefined;
|
|
20
33
|
tokensToday?: number | undefined;
|
|
34
|
+
monthStamp?: string | undefined;
|
|
35
|
+
spendThisMonthNanos?: number | undefined;
|
|
36
|
+
tokensThisMonth?: number | undefined;
|
|
21
37
|
reservedTodayNanos?: number | undefined;
|
|
38
|
+
reservedMonthNanos?: number | undefined;
|
|
22
39
|
reservedTotalNanos?: number | undefined;
|
|
23
40
|
reservedTodayTokens?: number | undefined;
|
|
41
|
+
reservedMonthTokens?: number | undefined;
|
|
24
42
|
reservedTotalTokens?: number | undefined;
|
|
25
43
|
pendingCount?: number | undefined;
|
|
26
|
-
|
|
44
|
+
dimension: string;
|
|
45
|
+
value: string;
|
|
27
46
|
totalSpendNanos: number;
|
|
28
47
|
totalRequests: number;
|
|
29
48
|
totalTokens: number;
|
|
30
49
|
dayStamp: string;
|
|
31
50
|
spendTodayNanos: number;
|
|
32
51
|
}, {
|
|
33
|
-
|
|
52
|
+
dimension: import("convex/values").VString<string, "required">;
|
|
53
|
+
value: import("convex/values").VString<string, "required">;
|
|
34
54
|
requestsPerMinute: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
55
|
+
maxConcurrent: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
35
56
|
dailySpendLimitNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
57
|
+
monthlySpendLimitNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
36
58
|
lifetimeSpendLimitNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
37
59
|
dailyTokenLimit: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
60
|
+
monthlyTokenLimit: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
38
61
|
lifetimeTokenLimit: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
39
62
|
blocked: import("convex/values").VBoolean<boolean | undefined, "optional">;
|
|
63
|
+
warnAtPct: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
40
64
|
enforcement: import("convex/values").VUnion<"hard" | "soft" | undefined, [import("convex/values").VLiteral<"hard", "required">, import("convex/values").VLiteral<"soft", "required">], "optional", never>;
|
|
41
65
|
dailyBumpNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
66
|
+
monthlyBumpNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
42
67
|
lifetimeBumpNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
43
68
|
bumpDayStamp: import("convex/values").VString<string | undefined, "optional">;
|
|
69
|
+
bumpMonthStamp: import("convex/values").VString<string | undefined, "optional">;
|
|
44
70
|
totalSpendNanos: import("convex/values").VFloat64<number, "required">;
|
|
45
71
|
totalRequests: import("convex/values").VFloat64<number, "required">;
|
|
46
72
|
totalTokens: import("convex/values").VFloat64<number, "required">;
|
|
47
73
|
dayStamp: import("convex/values").VString<string, "required">;
|
|
48
74
|
spendTodayNanos: import("convex/values").VFloat64<number, "required">;
|
|
49
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">;
|
|
50
79
|
reservedTodayNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
80
|
+
reservedMonthNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
51
81
|
reservedTotalNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
52
82
|
reservedTodayTokens: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
83
|
+
reservedMonthTokens: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
53
84
|
reservedTotalTokens: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
54
85
|
pendingCount: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
55
|
-
}, "required", "
|
|
56
|
-
|
|
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">, {
|
|
87
|
+
dim_value: ["dimension", "value", "_creationTime"];
|
|
88
|
+
dimension: ["dimension", "_creationTime"];
|
|
57
89
|
}, {}, {}>;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
bumpDayStamp?: string | undefined;
|
|
67
|
-
tokensToday?: number | undefined;
|
|
68
|
-
reservedTodayNanos?: number | undefined;
|
|
69
|
-
reservedTotalNanos?: number | undefined;
|
|
70
|
-
reservedTodayTokens?: number | undefined;
|
|
71
|
-
reservedTotalTokens?: number | undefined;
|
|
72
|
-
pendingCount?: number | undefined;
|
|
73
|
-
disabled?: boolean | undefined;
|
|
74
|
-
totalSpendNanos: number;
|
|
75
|
-
totalRequests: number;
|
|
76
|
-
totalTokens: number;
|
|
77
|
-
dayStamp: string;
|
|
78
|
-
spendTodayNanos: number;
|
|
79
|
-
name: string;
|
|
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;
|
|
80
98
|
}, {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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"];
|
|
104
136
|
}, {}, {}>;
|
|
105
137
|
requests: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
106
138
|
actionName?: string | undefined;
|
|
139
|
+
tags?: {
|
|
140
|
+
dimension: string;
|
|
141
|
+
value: string;
|
|
142
|
+
}[] | undefined;
|
|
107
143
|
estimatedNanos?: number | undefined;
|
|
108
144
|
estimatedTokens?: number | undefined;
|
|
109
145
|
unpricedModel?: boolean | undefined;
|
|
@@ -114,6 +150,7 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
114
150
|
promptTokens?: number | undefined;
|
|
115
151
|
completionTokens?: number | undefined;
|
|
116
152
|
cachedTokens?: number | undefined;
|
|
153
|
+
serverToolUses?: Record<string, number> | undefined;
|
|
117
154
|
costNanos?: number | undefined;
|
|
118
155
|
latencyMs?: number | undefined;
|
|
119
156
|
rerunOf?: import("convex/values").GenericId<"requests"> | undefined;
|
|
@@ -127,6 +164,16 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
127
164
|
}, {
|
|
128
165
|
userId: import("convex/values").VString<string, "required">;
|
|
129
166
|
actionName: import("convex/values").VString<string | undefined, "optional">;
|
|
167
|
+
tags: import("convex/values").VArray<{
|
|
168
|
+
dimension: string;
|
|
169
|
+
value: string;
|
|
170
|
+
}[] | undefined, import("convex/values").VObject<{
|
|
171
|
+
dimension: string;
|
|
172
|
+
value: string;
|
|
173
|
+
}, {
|
|
174
|
+
dimension: import("convex/values").VString<string, "required">;
|
|
175
|
+
value: import("convex/values").VString<string, "required">;
|
|
176
|
+
}, "required", "dimension" | "value">, "optional">;
|
|
130
177
|
model: import("convex/values").VString<string, "required">;
|
|
131
178
|
estimatedNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
132
179
|
estimatedTokens: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
@@ -149,10 +196,11 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
149
196
|
promptTokens: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
150
197
|
completionTokens: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
151
198
|
cachedTokens: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
199
|
+
serverToolUses: import("convex/values").VRecord<Record<string, number> | undefined, import("convex/values").VString<string, "required">, import("convex/values").VFloat64<number, "required">, "optional", string>;
|
|
152
200
|
costNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
153
201
|
latencyMs: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
154
202
|
rerunOf: import("convex/values").VId<import("convex/values").GenericId<"requests"> | undefined, "optional">;
|
|
155
|
-
}, "required", "userId" | "actionName" | "model" | "estimatedNanos" | "estimatedTokens" | "unpricedModel" | "overBudget" | "settled" | "messages" | "status" | "error" | "responseText" | "promptTokens" | "completionTokens" | "cachedTokens" | "costNanos" | "latencyMs" | "rerunOf"
|
|
203
|
+
}, "required", "userId" | "actionName" | "tags" | "model" | "estimatedNanos" | "estimatedTokens" | "unpricedModel" | "overBudget" | "settled" | "messages" | "status" | "error" | "responseText" | "promptTokens" | "completionTokens" | "cachedTokens" | "serverToolUses" | "costNanos" | "latencyMs" | "rerunOf" | `serverToolUses.${string}`>, {
|
|
156
204
|
userId: ["userId", "_creationTime"];
|
|
157
205
|
status: ["status", "_creationTime"];
|
|
158
206
|
rerunOf: ["rerunOf", "_creationTime"];
|
|
@@ -160,6 +208,7 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
160
208
|
settled: ["settled", "_creationTime"];
|
|
161
209
|
}, {}, {}>;
|
|
162
210
|
prices: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
211
|
+
cachedNanosPerMTok?: number | undefined;
|
|
163
212
|
model: string;
|
|
164
213
|
inputNanosPerMTok: number;
|
|
165
214
|
outputNanosPerMTok: number;
|
|
@@ -167,7 +216,8 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
167
216
|
model: import("convex/values").VString<string, "required">;
|
|
168
217
|
inputNanosPerMTok: import("convex/values").VFloat64<number, "required">;
|
|
169
218
|
outputNanosPerMTok: import("convex/values").VFloat64<number, "required">;
|
|
170
|
-
|
|
219
|
+
cachedNanosPerMTok: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
220
|
+
}, "required", "model" | "inputNanosPerMTok" | "outputNanosPerMTok" | "cachedNanosPerMTok">, {
|
|
171
221
|
model: ["model", "_creationTime"];
|
|
172
222
|
}, {}, {}>;
|
|
173
223
|
settings: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
@@ -180,6 +230,8 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
180
230
|
globalLifetimeBumpNanos?: number | undefined;
|
|
181
231
|
globalBumpDayStamp?: string | undefined;
|
|
182
232
|
retentionMs?: number | undefined;
|
|
233
|
+
defaultWarnAtPct?: number | undefined;
|
|
234
|
+
serverToolPrices?: Record<string, number> | undefined;
|
|
183
235
|
key: string;
|
|
184
236
|
}, {
|
|
185
237
|
key: import("convex/values").VString<string, "required">;
|
|
@@ -192,7 +244,9 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
192
244
|
globalLifetimeBumpNanos: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
193
245
|
globalBumpDayStamp: import("convex/values").VString<string | undefined, "optional">;
|
|
194
246
|
retentionMs: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
195
|
-
|
|
247
|
+
defaultWarnAtPct: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
248
|
+
serverToolPrices: import("convex/values").VRecord<Record<string, number> | undefined, import("convex/values").VString<string, "required">, import("convex/values").VFloat64<number, "required">, "optional", string>;
|
|
249
|
+
}, "required", "key" | "modelMode" | "models" | "globalDailySpendLimitNanos" | "globalLifetimeSpendLimitNanos" | "globalEnforcement" | "globalDailyBumpNanos" | "globalLifetimeBumpNanos" | "globalBumpDayStamp" | "retentionMs" | "defaultWarnAtPct" | "serverToolPrices" | `serverToolPrices.${string}`>, {
|
|
196
250
|
key: ["key", "_creationTime"];
|
|
197
251
|
}, {}, {}>;
|
|
198
252
|
}, true>;
|
package/dist/component/schema.js
CHANGED
|
@@ -4,65 +4,102 @@ export const vMessage = v.object({
|
|
|
4
4
|
role: v.string(), // "system" | "user" | "assistant" | "tool"
|
|
5
5
|
content: v.string(),
|
|
6
6
|
});
|
|
7
|
+
// One attribution tag: a (dimension, value) pair, e.g. {dimension:"user",
|
|
8
|
+
// value:"alice"} or {dimension:"customer", value:"acme"}. `user` and `action`
|
|
9
|
+
// are built-in dimensions; apps can add any others (team, project, env, …).
|
|
10
|
+
export const vTag = v.object({ dimension: v.string(), value: v.string() });
|
|
7
11
|
export default defineSchema({
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
// A budget holder, keyed by (dimension, value). Unifies what used to be the
|
|
13
|
+
// `users` and `actions` tables — those are just the "user" and "action"
|
|
14
|
+
// dimensions now. Any tag a request carries can have its own budget here.
|
|
15
|
+
buckets: defineTable({
|
|
16
|
+
dimension: v.string(),
|
|
17
|
+
value: v.string(),
|
|
10
18
|
// limits (all optional — unlimited by default)
|
|
11
|
-
requestsPerMinute: v.optional(v.number()),
|
|
19
|
+
requestsPerMinute: v.optional(v.number()), // rolling limit for this bucket
|
|
20
|
+
maxConcurrent: v.optional(v.number()), // max in-flight (pending) requests
|
|
12
21
|
dailySpendLimitNanos: v.optional(v.number()),
|
|
22
|
+
monthlySpendLimitNanos: v.optional(v.number()),
|
|
13
23
|
lifetimeSpendLimitNanos: v.optional(v.number()),
|
|
14
24
|
dailyTokenLimit: v.optional(v.number()),
|
|
25
|
+
monthlyTokenLimit: v.optional(v.number()),
|
|
15
26
|
lifetimeTokenLimit: v.optional(v.number()),
|
|
16
|
-
blocked: v.optional(v.boolean()),
|
|
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()),
|
|
17
31
|
// "hard" (default): exceeding a budget blocks. "soft": warn but allow.
|
|
18
32
|
enforcement: v.optional(v.union(v.literal("hard"), v.literal("soft"))),
|
|
19
|
-
// one-time bumps ("approve another $X")
|
|
20
|
-
//
|
|
33
|
+
// one-time bumps ("approve another $X"). Daily/monthly bumps are scoped to
|
|
34
|
+
// their stamp (reset with the window); lifetime bump is permanent.
|
|
21
35
|
dailyBumpNanos: v.optional(v.number()),
|
|
36
|
+
monthlyBumpNanos: v.optional(v.number()),
|
|
22
37
|
lifetimeBumpNanos: v.optional(v.number()),
|
|
23
38
|
bumpDayStamp: v.optional(v.string()),
|
|
39
|
+
bumpMonthStamp: v.optional(v.string()),
|
|
24
40
|
// settled totals (from finished requests)
|
|
25
41
|
totalSpendNanos: v.number(),
|
|
26
42
|
totalRequests: v.number(),
|
|
27
43
|
totalTokens: v.number(),
|
|
28
44
|
// daily window
|
|
29
|
-
dayStamp: v.string(), // e.g. "2026-08-27" (UTC)
|
|
30
|
-
spendTodayNanos: v.number(),
|
|
31
|
-
tokensToday: v.optional(v.number()),
|
|
32
|
-
// in-flight reservations (pessimistic holds; released on settle/expiry)
|
|
33
|
-
reservedTodayNanos: v.optional(v.number()),
|
|
34
|
-
reservedTotalNanos: v.optional(v.number()),
|
|
35
|
-
reservedTodayTokens: v.optional(v.number()),
|
|
36
|
-
reservedTotalTokens: v.optional(v.number()),
|
|
37
|
-
pendingCount: v.optional(v.number()),
|
|
38
|
-
}).index("userId", ["userId"]),
|
|
39
|
-
// per-action-name budgets and running totals (e.g. "chat", "summarize")
|
|
40
|
-
actions: defineTable({
|
|
41
|
-
name: v.string(),
|
|
42
|
-
dailySpendLimitNanos: v.optional(v.number()),
|
|
43
|
-
lifetimeSpendLimitNanos: v.optional(v.number()),
|
|
44
|
-
dailyTokenLimit: v.optional(v.number()),
|
|
45
|
-
lifetimeTokenLimit: v.optional(v.number()),
|
|
46
|
-
disabled: v.optional(v.boolean()),
|
|
47
|
-
enforcement: v.optional(v.union(v.literal("hard"), v.literal("soft"))),
|
|
48
|
-
dailyBumpNanos: v.optional(v.number()),
|
|
49
|
-
lifetimeBumpNanos: v.optional(v.number()),
|
|
50
|
-
bumpDayStamp: v.optional(v.string()),
|
|
51
|
-
totalSpendNanos: v.number(),
|
|
52
|
-
totalRequests: v.number(),
|
|
53
|
-
totalTokens: v.number(),
|
|
54
45
|
dayStamp: v.string(),
|
|
55
46
|
spendTodayNanos: v.number(),
|
|
56
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()),
|
|
52
|
+
// in-flight reservations (pessimistic holds; released on settle/expiry)
|
|
57
53
|
reservedTodayNanos: v.optional(v.number()),
|
|
54
|
+
reservedMonthNanos: v.optional(v.number()),
|
|
58
55
|
reservedTotalNanos: v.optional(v.number()),
|
|
59
56
|
reservedTodayTokens: v.optional(v.number()),
|
|
57
|
+
reservedMonthTokens: v.optional(v.number()),
|
|
60
58
|
reservedTotalTokens: v.optional(v.number()),
|
|
61
59
|
pendingCount: v.optional(v.number()),
|
|
62
|
-
})
|
|
60
|
+
})
|
|
61
|
+
.index("dim_value", ["dimension", "value"])
|
|
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"]),
|
|
63
97
|
requests: defineTable({
|
|
98
|
+
// `user` and `action` stay first-class + indexed (the hot-path filters and
|
|
99
|
+
// rate limiting); the full attribution incl. extra tags lives in `tags`.
|
|
64
100
|
userId: v.string(),
|
|
65
101
|
actionName: v.optional(v.string()),
|
|
102
|
+
tags: v.optional(v.array(vTag)),
|
|
66
103
|
model: v.string(),
|
|
67
104
|
// pessimistic holds placed at start; reconciled to actual on settle
|
|
68
105
|
estimatedNanos: v.optional(v.number()),
|
|
@@ -83,6 +120,9 @@ export default defineSchema({
|
|
|
83
120
|
completionTokens: v.optional(v.number()),
|
|
84
121
|
// subset of promptTokens served from the provider's prompt cache (cheaper).
|
|
85
122
|
cachedTokens: v.optional(v.number()),
|
|
123
|
+
// server-side tool invocations that bill a per-call fee on top of tokens
|
|
124
|
+
// (e.g. { web_search: 3 }). Priced via serverToolPrices at settle.
|
|
125
|
+
serverToolUses: v.optional(v.record(v.string(), v.number())),
|
|
86
126
|
costNanos: v.optional(v.number()),
|
|
87
127
|
latencyMs: v.optional(v.number()),
|
|
88
128
|
rerunOf: v.optional(v.id("requests")),
|
|
@@ -97,6 +137,9 @@ export default defineSchema({
|
|
|
97
137
|
model: v.string(),
|
|
98
138
|
inputNanosPerMTok: v.number(),
|
|
99
139
|
outputNanosPerMTok: v.number(),
|
|
140
|
+
// price for cached (prompt-cache-read) input tokens. Providers bill these
|
|
141
|
+
// at a fraction of the input rate; if unset, a default discount is applied.
|
|
142
|
+
cachedNanosPerMTok: v.optional(v.number()),
|
|
100
143
|
}).index("model", ["model"]),
|
|
101
144
|
// singleton component config (key === "singleton")
|
|
102
145
|
settings: defineTable({
|
|
@@ -105,12 +148,12 @@ export default defineSchema({
|
|
|
105
148
|
// "denylist": any model except the listed ones.
|
|
106
149
|
modelMode: v.optional(v.union(v.literal("open"), v.literal("allowlist"), v.literal("denylist"))),
|
|
107
150
|
models: v.optional(v.array(v.string())),
|
|
108
|
-
// Deployment-wide ("global") spend cap across ALL
|
|
109
|
-
//
|
|
110
|
-
// the limit config lives here.
|
|
111
|
-
// sharded total is read without a reservation, so under heavy
|
|
112
|
-
// it can overshoot by a bounded amount. Right for a global
|
|
113
|
-
// per-
|
|
151
|
+
// Deployment-wide ("global") spend cap across ALL requests. Running totals
|
|
152
|
+
// live in a sharded counter (high write throughput) since every request
|
|
153
|
+
// touches it; only the limit config lives here. Enforced approximately —
|
|
154
|
+
// the sharded total is read without a reservation, so under heavy
|
|
155
|
+
// concurrency it can overshoot by a bounded amount. Right for a global
|
|
156
|
+
// killswitch; per-bucket concurrent admission is atomic via reserve/settle.
|
|
114
157
|
globalDailySpendLimitNanos: v.optional(v.number()),
|
|
115
158
|
globalLifetimeSpendLimitNanos: v.optional(v.number()),
|
|
116
159
|
globalEnforcement: v.optional(v.union(v.literal("hard"), v.literal("soft"))),
|
|
@@ -119,5 +162,11 @@ export default defineSchema({
|
|
|
119
162
|
globalBumpDayStamp: v.optional(v.string()),
|
|
120
163
|
// request-row retention window in ms (default 1h); 0 disables sweeping.
|
|
121
164
|
retentionMs: v.optional(v.number()),
|
|
165
|
+
// default approaching-limit alert threshold (fraction of a cap) for buckets
|
|
166
|
+
// that don't set their own warnAtPct. 0/unset disables threshold alerts.
|
|
167
|
+
defaultWarnAtPct: v.optional(v.number()),
|
|
168
|
+
// per-call price (nanodollars) overrides for provider server tools, keyed by
|
|
169
|
+
// tool name (e.g. { web_search: 12_000_000 }). Merged over the defaults.
|
|
170
|
+
serverToolPrices: v.optional(v.record(v.string(), v.number())),
|
|
122
171
|
}).index("key", ["key"]),
|
|
123
172
|
});
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"email": "support@convex.dev",
|
|
8
8
|
"url": "https://github.com/get-convex/ai-budget/issues"
|
|
9
9
|
},
|
|
10
|
-
"version": "0.0.2-alpha.
|
|
10
|
+
"version": "0.0.2-alpha.12",
|
|
11
11
|
"license": "Apache-2.0",
|
|
12
12
|
"type": "module",
|
|
13
13
|
"keywords": [
|
|
@@ -64,10 +64,10 @@
|
|
|
64
64
|
"convex": "^1.45.0"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@edge-runtime/vm": "
|
|
68
|
-
"convex": "
|
|
69
|
-
"convex-test": "
|
|
67
|
+
"@edge-runtime/vm": "5.0.0",
|
|
68
|
+
"convex": "1.45.0",
|
|
69
|
+
"convex-test": "0.0.56",
|
|
70
70
|
"typescript": "^5.7.0",
|
|
71
|
-
"vitest": "
|
|
71
|
+
"vitest": "4.1.11"
|
|
72
72
|
}
|
|
73
73
|
}
|