@convex-dev/ai-budget 0.0.2-alpha.4 → 0.0.2-alpha.6

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.
@@ -20,18 +20,26 @@ export default defineSchema({
20
20
  value: v.string(),
21
21
  // limits (all optional — unlimited by default)
22
22
  requestsPerMinute: v.optional(v.number()), // enforced on the "user" dimension
23
+ maxConcurrent: v.optional(v.number()), // max in-flight (pending) requests
23
24
  dailySpendLimitNanos: v.optional(v.number()),
25
+ monthlySpendLimitNanos: v.optional(v.number()),
24
26
  lifetimeSpendLimitNanos: v.optional(v.number()),
25
27
  dailyTokenLimit: v.optional(v.number()),
28
+ monthlyTokenLimit: v.optional(v.number()),
26
29
  lifetimeTokenLimit: v.optional(v.number()),
27
30
  blocked: v.optional(v.boolean()), // hard block (was `blocked`/`disabled`)
31
+ // Fire an approaching-limit alert once usage crosses this fraction of a cap
32
+ // (e.g. 0.8 = warn at 80%). Falls back to the deployment default.
33
+ warnAtPct: v.optional(v.number()),
28
34
  // "hard" (default): exceeding a budget blocks. "soft": warn but allow.
29
35
  enforcement: v.optional(v.union(v.literal("hard"), v.literal("soft"))),
30
- // one-time bumps ("approve another $X"). Daily bump is scoped to bumpDayStamp
31
- // (resets with the day); lifetime bump is permanent.
36
+ // one-time bumps ("approve another $X"). Daily/monthly bumps are scoped to
37
+ // their stamp (reset with the window); lifetime bump is permanent.
32
38
  dailyBumpNanos: v.optional(v.number()),
39
+ monthlyBumpNanos: v.optional(v.number()),
33
40
  lifetimeBumpNanos: v.optional(v.number()),
34
41
  bumpDayStamp: v.optional(v.string()),
42
+ bumpMonthStamp: v.optional(v.string()),
35
43
  // settled totals (from finished requests)
36
44
  totalSpendNanos: v.number(),
37
45
  totalRequests: v.number(),
@@ -40,16 +48,59 @@ export default defineSchema({
40
48
  dayStamp: v.string(),
41
49
  spendTodayNanos: v.number(),
42
50
  tokensToday: v.optional(v.number()),
51
+ // monthly window (UTC calendar month, e.g. "2026-09")
52
+ monthStamp: v.optional(v.string()),
53
+ spendThisMonthNanos: v.optional(v.number()),
54
+ tokensThisMonth: v.optional(v.number()),
43
55
  // in-flight reservations (pessimistic holds; released on settle/expiry)
44
56
  reservedTodayNanos: v.optional(v.number()),
57
+ reservedMonthNanos: v.optional(v.number()),
45
58
  reservedTotalNanos: v.optional(v.number()),
46
59
  reservedTodayTokens: v.optional(v.number()),
60
+ reservedMonthTokens: v.optional(v.number()),
47
61
  reservedTotalTokens: v.optional(v.number()),
48
62
  pendingCount: v.optional(v.number()),
49
63
  })
50
64
  .index("dim_value", ["dimension", "value"])
51
65
  .index("dimension", ["dimension"]),
52
66
 
67
+ // Durable per-(bucket, period) spend history. Written from settled requests
68
+ // and manual adjustments; NEVER swept by request retention, so spend charts
69
+ // and "what did we spend last month" survive long after the raw request rows
70
+ // are purged. period is "day" ("2026-09-04") or "month" ("2026-09").
71
+ usage: defineTable({
72
+ dimension: v.string(),
73
+ value: v.string(),
74
+ period: v.union(v.literal("day"), v.literal("month")),
75
+ stamp: v.string(),
76
+ spendNanos: v.number(),
77
+ tokens: v.number(),
78
+ requests: v.number(),
79
+ })
80
+ .index("bucket_period_stamp", ["dimension", "value", "period", "stamp"])
81
+ .index("period_stamp", ["period", "stamp"]),
82
+
83
+ // Reverse index for filtering the request log by an arbitrary tag dimension
84
+ // (user/action are already indexed on `requests`). One row per extra tag per
85
+ // request; cleaned up with the request on retention/deletion.
86
+ requestTags: defineTable({
87
+ dimension: v.string(),
88
+ value: v.string(),
89
+ requestId: v.id("requests"),
90
+ })
91
+ .index("dim_value", ["dimension", "value"])
92
+ .index("requestId", ["requestId"]),
93
+
94
+ // Manual credits/debits applied to a bucket (comp a user, correct an
95
+ // overcharge). Negative delta = credit/refund, positive = extra charge.
96
+ adjustments: defineTable({
97
+ dimension: v.string(),
98
+ value: v.string(),
99
+ deltaNanos: v.number(),
100
+ tokens: v.optional(v.number()),
101
+ reason: v.optional(v.string()),
102
+ }).index("dim_value", ["dimension", "value"]),
103
+
53
104
  requests: defineTable({
54
105
  // `user` and `action` stay first-class + indexed (the hot-path filters and
55
106
  // rate limiting); the full attribution incl. extra tags lives in `tags`.
@@ -96,6 +147,9 @@ export default defineSchema({
96
147
  model: v.string(),
97
148
  inputNanosPerMTok: v.number(),
98
149
  outputNanosPerMTok: v.number(),
150
+ // price for cached (prompt-cache-read) input tokens. Providers bill these
151
+ // at a fraction of the input rate; if unset, a default discount is applied.
152
+ cachedNanosPerMTok: v.optional(v.number()),
99
153
  }).index("model", ["model"]),
100
154
 
101
155
  // singleton component config (key === "singleton")
@@ -127,5 +181,8 @@ export default defineSchema({
127
181
  globalBumpDayStamp: v.optional(v.string()),
128
182
  // request-row retention window in ms (default 1h); 0 disables sweeping.
129
183
  retentionMs: v.optional(v.number()),
184
+ // default approaching-limit alert threshold (fraction of a cap) for buckets
185
+ // that don't set their own warnAtPct. 0/unset disables threshold alerts.
186
+ defaultWarnAtPct: v.optional(v.number()),
130
187
  }).index("key", ["key"]),
131
188
  });