@rudderjs/ai 1.4.0 → 1.6.0

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.
Files changed (179) hide show
  1. package/README.md +484 -7
  2. package/boost/guidelines.md +62 -2
  3. package/boost/skills/ai-tools/SKILL.md +14 -5
  4. package/dist/agent.d.ts +66 -15
  5. package/dist/agent.d.ts.map +1 -1
  6. package/dist/agent.js +529 -58
  7. package/dist/agent.js.map +1 -1
  8. package/dist/budget/pricing.d.ts +124 -0
  9. package/dist/budget/pricing.d.ts.map +1 -0
  10. package/dist/budget/pricing.js +175 -0
  11. package/dist/budget/pricing.js.map +1 -0
  12. package/dist/budget/storage.d.ts +104 -0
  13. package/dist/budget/storage.d.ts.map +1 -0
  14. package/dist/budget/storage.js +0 -0
  15. package/dist/budget/storage.js.map +1 -0
  16. package/dist/budget/with-budget.d.ts +119 -0
  17. package/dist/budget/with-budget.d.ts.map +1 -0
  18. package/dist/budget/with-budget.js +175 -0
  19. package/dist/budget/with-budget.js.map +1 -0
  20. package/dist/budget-orm/index.d.ts +96 -0
  21. package/dist/budget-orm/index.d.ts.map +1 -0
  22. package/dist/budget-orm/index.js +177 -0
  23. package/dist/budget-orm/index.js.map +1 -0
  24. package/dist/commands/ai-eval.d.ts +93 -0
  25. package/dist/commands/ai-eval.d.ts.map +1 -0
  26. package/dist/commands/ai-eval.js +378 -0
  27. package/dist/commands/ai-eval.js.map +1 -0
  28. package/dist/computer-use/actions.d.ts +214 -0
  29. package/dist/computer-use/actions.d.ts.map +1 -0
  30. package/dist/computer-use/actions.js +48 -0
  31. package/dist/computer-use/actions.js.map +1 -0
  32. package/dist/computer-use/errors.d.ts +57 -0
  33. package/dist/computer-use/errors.d.ts.map +1 -0
  34. package/dist/computer-use/errors.js +76 -0
  35. package/dist/computer-use/errors.js.map +1 -0
  36. package/dist/computer-use/index.d.ts +53 -0
  37. package/dist/computer-use/index.d.ts.map +1 -0
  38. package/dist/computer-use/index.js +51 -0
  39. package/dist/computer-use/index.js.map +1 -0
  40. package/dist/computer-use/playwright.d.ts +76 -0
  41. package/dist/computer-use/playwright.d.ts.map +1 -0
  42. package/dist/computer-use/playwright.js +270 -0
  43. package/dist/computer-use/playwright.js.map +1 -0
  44. package/dist/computer-use/tool.d.ts +154 -0
  45. package/dist/computer-use/tool.d.ts.map +1 -0
  46. package/dist/computer-use/tool.js +210 -0
  47. package/dist/computer-use/tool.js.map +1 -0
  48. package/dist/eval/fixtures.d.ts +65 -0
  49. package/dist/eval/fixtures.d.ts.map +1 -0
  50. package/dist/eval/fixtures.js +110 -0
  51. package/dist/eval/fixtures.js.map +1 -0
  52. package/dist/eval/html-reporter.d.ts +25 -0
  53. package/dist/eval/html-reporter.d.ts.map +1 -0
  54. package/dist/eval/html-reporter.js +209 -0
  55. package/dist/eval/html-reporter.js.map +1 -0
  56. package/dist/eval/index.d.ts +271 -0
  57. package/dist/eval/index.d.ts.map +1 -0
  58. package/dist/eval/index.js +510 -0
  59. package/dist/eval/index.js.map +1 -0
  60. package/dist/eval/json-reporter.d.ts +43 -0
  61. package/dist/eval/json-reporter.d.ts.map +1 -0
  62. package/dist/eval/json-reporter.js +40 -0
  63. package/dist/eval/json-reporter.js.map +1 -0
  64. package/dist/fake.d.ts +36 -1
  65. package/dist/fake.d.ts.map +1 -1
  66. package/dist/fake.js +49 -2
  67. package/dist/fake.js.map +1 -1
  68. package/dist/file-search.d.ts +168 -0
  69. package/dist/file-search.d.ts.map +1 -0
  70. package/dist/file-search.js +158 -0
  71. package/dist/file-search.js.map +1 -0
  72. package/dist/handoff.d.ts +95 -0
  73. package/dist/handoff.d.ts.map +1 -0
  74. package/dist/handoff.js +78 -0
  75. package/dist/handoff.js.map +1 -0
  76. package/dist/index.d.ts +29 -5
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +22 -2
  79. package/dist/index.js.map +1 -1
  80. package/dist/mcp/client-tools.d.ts +39 -0
  81. package/dist/mcp/client-tools.d.ts.map +1 -0
  82. package/dist/mcp/client-tools.js +147 -0
  83. package/dist/mcp/client-tools.js.map +1 -0
  84. package/dist/mcp/index.d.ts +16 -0
  85. package/dist/mcp/index.d.ts.map +1 -0
  86. package/dist/mcp/index.js +15 -0
  87. package/dist/mcp/index.js.map +1 -0
  88. package/dist/mcp/server-from-agent.d.ts +24 -0
  89. package/dist/mcp/server-from-agent.d.ts.map +1 -0
  90. package/dist/mcp/server-from-agent.js +113 -0
  91. package/dist/mcp/server-from-agent.js.map +1 -0
  92. package/dist/mcp/types.d.ts +64 -0
  93. package/dist/mcp/types.d.ts.map +1 -0
  94. package/dist/mcp/types.js +6 -0
  95. package/dist/mcp/types.js.map +1 -0
  96. package/dist/memory-embedding/index.d.ts +121 -0
  97. package/dist/memory-embedding/index.d.ts.map +1 -0
  98. package/dist/memory-embedding/index.js +229 -0
  99. package/dist/memory-embedding/index.js.map +1 -0
  100. package/dist/memory-extract.d.ts +60 -0
  101. package/dist/memory-extract.d.ts.map +1 -0
  102. package/dist/memory-extract.js +163 -0
  103. package/dist/memory-extract.js.map +1 -0
  104. package/dist/memory-inject.d.ts +39 -0
  105. package/dist/memory-inject.d.ts.map +1 -0
  106. package/dist/memory-inject.js +135 -0
  107. package/dist/memory-inject.js.map +1 -0
  108. package/dist/memory-orm/index.d.ts +118 -0
  109. package/dist/memory-orm/index.d.ts.map +1 -0
  110. package/dist/memory-orm/index.js +187 -0
  111. package/dist/memory-orm/index.js.map +1 -0
  112. package/dist/memory.d.ts +55 -0
  113. package/dist/memory.d.ts.map +1 -0
  114. package/dist/memory.js +132 -0
  115. package/dist/memory.js.map +1 -0
  116. package/dist/observers.d.ts +22 -0
  117. package/dist/observers.d.ts.map +1 -1
  118. package/dist/observers.js.map +1 -1
  119. package/dist/provider-tools.d.ts +15 -1
  120. package/dist/provider-tools.d.ts.map +1 -1
  121. package/dist/provider-tools.js +21 -1
  122. package/dist/provider-tools.js.map +1 -1
  123. package/dist/providers/anthropic.d.ts +9 -1
  124. package/dist/providers/anthropic.d.ts.map +1 -1
  125. package/dist/providers/anthropic.js +66 -11
  126. package/dist/providers/anthropic.js.map +1 -1
  127. package/dist/providers/bedrock.d.ts +60 -0
  128. package/dist/providers/bedrock.d.ts.map +1 -0
  129. package/dist/providers/bedrock.js +167 -0
  130. package/dist/providers/bedrock.js.map +1 -0
  131. package/dist/providers/elevenlabs.d.ts +98 -0
  132. package/dist/providers/elevenlabs.d.ts.map +1 -0
  133. package/dist/providers/elevenlabs.js +229 -0
  134. package/dist/providers/elevenlabs.js.map +1 -0
  135. package/dist/providers/google.d.ts +83 -1
  136. package/dist/providers/google.d.ts.map +1 -1
  137. package/dist/providers/google.js +491 -8
  138. package/dist/providers/google.js.map +1 -1
  139. package/dist/providers/openai.d.ts +8 -1
  140. package/dist/providers/openai.d.ts.map +1 -1
  141. package/dist/providers/openai.js +215 -5
  142. package/dist/providers/openai.js.map +1 -1
  143. package/dist/providers/openrouter.d.ts +43 -0
  144. package/dist/providers/openrouter.d.ts.map +1 -0
  145. package/dist/providers/openrouter.js +21 -0
  146. package/dist/providers/openrouter.js.map +1 -0
  147. package/dist/providers/voyage.d.ts +91 -0
  148. package/dist/providers/voyage.d.ts.map +1 -0
  149. package/dist/providers/voyage.js +166 -0
  150. package/dist/providers/voyage.js.map +1 -0
  151. package/dist/queue-job.d.ts +69 -4
  152. package/dist/queue-job.d.ts.map +1 -1
  153. package/dist/queue-job.js +114 -11
  154. package/dist/queue-job.js.map +1 -1
  155. package/dist/registry.d.ts +3 -1
  156. package/dist/registry.d.ts.map +1 -1
  157. package/dist/registry.js +10 -0
  158. package/dist/registry.js.map +1 -1
  159. package/dist/server/provider.d.ts.map +1 -1
  160. package/dist/server/provider.js +38 -1
  161. package/dist/server/provider.js.map +1 -1
  162. package/dist/similarity-search.d.ts +163 -0
  163. package/dist/similarity-search.d.ts.map +1 -0
  164. package/dist/similarity-search.js +147 -0
  165. package/dist/similarity-search.js.map +1 -0
  166. package/dist/sub-agent-run-store.d.ts +40 -3
  167. package/dist/sub-agent-run-store.d.ts.map +1 -1
  168. package/dist/sub-agent-run-store.js.map +1 -1
  169. package/dist/tool.d.ts +59 -0
  170. package/dist/tool.d.ts.map +1 -1
  171. package/dist/tool.js +45 -4
  172. package/dist/tool.js.map +1 -1
  173. package/dist/types.d.ts +285 -1
  174. package/dist/types.d.ts.map +1 -1
  175. package/dist/vector-stores/index.d.ts +96 -0
  176. package/dist/vector-stores/index.d.ts.map +1 -0
  177. package/dist/vector-stores/index.js +153 -0
  178. package/dist/vector-stores/index.js.map +1 -0
  179. package/package.json +43 -4
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Persistence contract for `withBudget(...)` middleware (#A6 Phase 3).
3
+ *
4
+ * Apps configure a `BudgetStorage` instance — the middleware calls
5
+ * `checkAndDebit()` once before each request (estimated input cost) and
6
+ * once per `usage` chunk (actual output cost). The storage is responsible
7
+ * for atomic increment + cap enforcement.
8
+ *
9
+ * # Atomicity
10
+ *
11
+ * `checkAndDebit` MUST be atomic: a concurrent caller cannot observe a
12
+ * stale `spent` value between the read and the write. Otherwise two
13
+ * requests both pass the check before either debits, and a user can
14
+ * exceed their cap by `costUsd × concurrency`.
15
+ *
16
+ * Implementations:
17
+ * - `memoryBudgetStorage()` — single-process Map; atomic because we never
18
+ * `await` between the read and the write.
19
+ * - `ormBudgetStorage()` (Phase 4) — uses `UPDATE … RETURNING` (Postgres)
20
+ * or row-level `SELECT … FOR UPDATE` (MySQL/SQLite) to keep the read +
21
+ * write in a single transaction.
22
+ * - Redis / Cache impls — would use `INCRBY` + a `EXPIRE`, treating the
23
+ * counter as authoritative.
24
+ *
25
+ * # Period semantics
26
+ *
27
+ * `daily` rolls at the user's local midnight when `timezone` is provided
28
+ * (IANA name, e.g. `'America/New_York'`); UTC otherwise. `monthly` rolls
29
+ * on the calendar-month boundary in the same TZ. Phase 2 does not
30
+ * implement rolling-30-days windows — adds storage complexity for marginal
31
+ * value.
32
+ */
33
+ export type BudgetPeriod = 'daily' | 'monthly';
34
+ export interface BudgetCheckOptions {
35
+ /** Identifier for the budget owner (typically a user id). */
36
+ userId: string;
37
+ /** Which cap to check against. */
38
+ period: BudgetPeriod;
39
+ /** Cap in USD. Caller resolves `budget()` once per request. */
40
+ cap: number;
41
+ /** Cost to debit in USD. Pass `0` to read the current spent value without mutating. */
42
+ costUsd: number;
43
+ /** Override the clock — useful for tests + period-rollover assertions. */
44
+ now?: Date;
45
+ /** IANA timezone for period boundaries. Defaults to UTC. */
46
+ timezone?: string;
47
+ }
48
+ export interface BudgetCheckResult {
49
+ /** True when the debit was applied; false when it would have exceeded `cap`. */
50
+ allowed: boolean;
51
+ /**
52
+ * Current spend for the period.
53
+ *
54
+ * - When `allowed: true`, this is the value AFTER the debit (i.e.
55
+ * includes `costUsd`).
56
+ * - When `allowed: false`, this is the value BEFORE the rejected debit
57
+ * (i.e. the prior `spent`). This matches the value `BudgetExceededError`
58
+ * should report — the user spent `spent`, the cap was `cap`, the
59
+ * request would have pushed past.
60
+ */
61
+ spent: number;
62
+ /** The cap passed in `BudgetCheckOptions.cap`, echoed for caller convenience. */
63
+ cap: number;
64
+ }
65
+ export interface BudgetStorage {
66
+ /**
67
+ * Atomically read the current `spent` value, add `costUsd` if the result
68
+ * stays within `cap`, and return the outcome.
69
+ *
70
+ * Pass `costUsd: 0` for a pure read (e.g. for a "you've spent $X today"
71
+ * status display) — the result reflects current spend without mutating.
72
+ */
73
+ checkAndDebit(opts: BudgetCheckOptions): Promise<BudgetCheckResult>;
74
+ /**
75
+ * Reset the budget counter for `(userId, period)` at the period
76
+ * containing `now` (or "now" by clock). Useful for tests and admin
77
+ * overrides; not required by the middleware.
78
+ */
79
+ reset?(userId: string, period: BudgetPeriod, now?: Date, timezone?: string): Promise<void>;
80
+ }
81
+ /**
82
+ * Format a `Date` as the bucket key for a billing period.
83
+ *
84
+ * - `daily` → `YYYY-MM-DD` in the given timezone (or UTC)
85
+ * - `monthly` → `YYYY-MM` in the given timezone (or UTC)
86
+ *
87
+ * Used by `memoryBudgetStorage` and `ormBudgetStorage` (Phase 4) to
88
+ * partition the counter by billing window. Exposed publicly so apps
89
+ * computing per-period totals outside the middleware (admin dashboards,
90
+ * tests) get the same bucketing.
91
+ */
92
+ export declare function periodKey(period: BudgetPeriod, now: Date, timezone?: string): string;
93
+ /**
94
+ * In-process `BudgetStorage` backed by a `Map`. Suitable for tests and
95
+ * single-process dev servers.
96
+ *
97
+ * **Cross-process caveat:** counters live in process-local memory, so
98
+ * queue workers (or any second process) see their own copy. For any app
99
+ * with workers or horizontal scaling, use `ormBudgetStorage()` (Phase 4)
100
+ * or a Redis-backed storage. Use `memoryBudgetStorage()` only when you're
101
+ * sure all budget-relevant code paths run in the same Node process.
102
+ */
103
+ export declare function memoryBudgetStorage(): BudgetStorage;
104
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/budget/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,SAAS,CAAA;AAE9C,MAAM,WAAW,kBAAkB;IACjC,6DAA6D;IAC7D,MAAM,EAAK,MAAM,CAAA;IACjB,kCAAkC;IAClC,MAAM,EAAK,YAAY,CAAA;IACvB,+DAA+D;IAC/D,GAAG,EAAQ,MAAM,CAAA;IACjB,uFAAuF;IACvF,OAAO,EAAI,MAAM,CAAA;IACjB,0EAA0E;IAC1E,GAAG,CAAC,EAAO,IAAI,CAAA;IACf,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,gFAAgF;IAChF,OAAO,EAAE,OAAO,CAAA;IAChB;;;;;;;;;OASG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,iFAAiF;IACjF,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,aAAa;IAC5B;;;;;;OAMG;IACH,aAAa,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAEnE;;;;OAIG;IACH,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAUpF;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,IAAI,aAAa,CAgCnD"}
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/budget/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAuDH;;;;;;;;;;GAUG;AACH,MAAM,UAAU,SAAS,CAAC,MAAoB,EAAE,GAAS,EAAE,QAAiB;IAC1E,kEAAkE;IAClE,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;QAC3C,QAAQ,EAAE,QAAQ,IAAI,KAAK;QAC3B,IAAI,EAAG,SAAS;QAChB,KAAK,EAAE,SAAS;QAChB,GAAG,EAAI,SAAS;KACjB,CAAC,CAAA;IACF,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA,CAAC,eAAe;IAC3C,OAAO,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA,CAAC,YAAY;AAChE,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB;IACjC,kEAAkE;IAClE,8DAA8D;IAC9D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAA;IACtC,MAAM,GAAG,GAAG,CAAC,MAAc,EAAE,MAAoB,EAAE,GAAS,EAAE,EAAW,EAAU,EAAE,CACnF,GAAG,MAAM,IAAI,MAAM,IAAI,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,CAAA;IAErD,OAAO;QACL,sEAAsE;QACtE,sEAAsE;QACtE,yEAAyE;QACzE,sEAAsE;QACtE,gEAAgE;QAChE,aAAa;QACb,KAAK,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE,EAAE,QAAQ,EAAE;YAC9E,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC;gBAAU,MAAM,IAAI,KAAK,CAAC,8EAA8E,GAAG,EAAE,CAAC,CAAA;YAClJ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,kFAAkF,OAAO,EAAE,CAAC,CAAA;YAE1J,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAA;YAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;YAChC,MAAM,SAAS,GAAG,OAAO,GAAG,OAAO,CAAA;YACnC,IAAI,SAAS,GAAG,GAAG,EAAE,CAAC;gBACpB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAA;YAChD,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,CAAA;YACtB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,CAAA;QACjD,CAAC;QAED,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE,EAAE,QAAQ;YACpD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAA;QACjD,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,119 @@
1
+ import type { AiMiddleware, MiddlewareContext } from '../types.js';
2
+ import { type ModelPriceEntry } from './pricing.js';
3
+ import type { BudgetPeriod, BudgetStorage } from './storage.js';
4
+ /**
5
+ * Per-period caps in USD. Either or both can be set; periods with `null`
6
+ * / `undefined` are not enforced.
7
+ */
8
+ export interface BudgetCaps {
9
+ daily?: number | null;
10
+ monthly?: number | null;
11
+ }
12
+ export interface BudgetExceededArgs {
13
+ userId: string;
14
+ period: BudgetPeriod;
15
+ /** Spend recorded BEFORE the rejected debit. */
16
+ spent: number;
17
+ cap: number;
18
+ ctx: MiddlewareContext;
19
+ }
20
+ export interface WithBudgetOptions {
21
+ /**
22
+ * Resolves the user identifier for a given request. Return `null` (or
23
+ * `undefined`) to bypass budget enforcement entirely — useful for
24
+ * unauthenticated paths or admin tooling.
25
+ *
26
+ * Async return supported.
27
+ */
28
+ user(ctx: MiddlewareContext): string | null | undefined | Promise<string | null | undefined>;
29
+ /**
30
+ * USD caps for the resolved user. Called once per request (per agent
31
+ * step, more precisely). Caps may be set per-tier / per-plan by reading
32
+ * the user from your DB inside the callback.
33
+ */
34
+ budget(args: {
35
+ userId: string;
36
+ ctx: MiddlewareContext;
37
+ }): BudgetCaps | Promise<BudgetCaps>;
38
+ /**
39
+ * Where counters persist. Use {@link memoryBudgetStorage} for tests +
40
+ * single-process dev; `ormBudgetStorage` (#A6 Phase 4) for production.
41
+ */
42
+ storage: BudgetStorage;
43
+ /**
44
+ * Pricing catalog. Defaults to the shipped {@link ModelPricing}; spread
45
+ * to override entries for negotiated rates:
46
+ *
47
+ * ```ts
48
+ * pricing: { ...ModelPricing, 'anthropic/claude-opus-4-7': { ... } }
49
+ * ```
50
+ */
51
+ pricing?: Record<string, ModelPriceEntry>;
52
+ /**
53
+ * Called when a debit would exceed a cap. Default throws
54
+ * {@link BudgetExceededError}; supply your own to log/alert before
55
+ * throwing, or to throw a different error class. Must throw — return
56
+ * value is ignored. The throw aborts the agent run before the model
57
+ * call.
58
+ */
59
+ onExceeded?(args: BudgetExceededArgs): never | Promise<never>;
60
+ /**
61
+ * IANA timezone for daily / monthly period rollover. Defaults to UTC.
62
+ * Use the user's tz to roll caps at user-local midnight (matches
63
+ * billing dashboards).
64
+ */
65
+ timezone?: string;
66
+ /**
67
+ * Approximate-tokens estimator used for the pre-debit. Defaults to
68
+ * `Math.ceil(text.length / 4)` — fine for English-heavy prompts. Pass
69
+ * a tiktoken-backed estimator for accuracy.
70
+ */
71
+ estimateTokens?: (text: string) => number;
72
+ }
73
+ /**
74
+ * Per-user spend cap middleware (#A6 Phase 3).
75
+ *
76
+ * Pre-debits an input-cost estimate before each provider call (refusing
77
+ * with {@link BudgetExceededError} if the user would exceed any
78
+ * configured cap), then debits the actual cost difference once the
79
+ * `usage` chunk arrives.
80
+ *
81
+ * The pre-debit reserves budget so two concurrent requests can't both
82
+ * pass the check before either is billed — the `BudgetStorage` contract
83
+ * (#A6 Phase 2) requires `checkAndDebit` to be atomic.
84
+ *
85
+ * # Example
86
+ *
87
+ * ```ts
88
+ * import { withBudget, memoryBudgetStorage, ModelPricing } from '@rudderjs/ai'
89
+ *
90
+ * const budgeted = withBudget({
91
+ * user: (ctx) => ctx.context as string, // your app's user-id source
92
+ * budget: () => ({ daily: 0.50, monthly: 10 }), // USD
93
+ * storage: memoryBudgetStorage(),
94
+ * pricing: ModelPricing,
95
+ * })
96
+ *
97
+ * class MyAgent extends Agent {
98
+ * middleware() { return [budgeted] }
99
+ * }
100
+ * ```
101
+ *
102
+ * # Caveats
103
+ *
104
+ * - **Refunds on errors are not issued.** If the provider call fails
105
+ * after the pre-debit, the estimate stays debited. This avoids the
106
+ * complexity of distinguishing partial-credit cases (the model may
107
+ * have produced output before erroring). Apps that need refund-on-error
108
+ * should subscribe via `onError` and call `storage` directly.
109
+ * - **Cache token deltas not counted.** `TokenUsage` does not yet expose
110
+ * `cacheReadInputTokens` / `cacheWriteInputTokens`; cached requests are
111
+ * billed at the full `inputPer1k` rate today. Refining this is a phase
112
+ * 3.x follow-up that needs a `TokenUsage` widening.
113
+ * - **Tokenizer differences.** The default token estimator is
114
+ * `text.length / 4`. Provider-reported `usage.promptTokens` may differ
115
+ * by a few percent. Pass `estimateTokens: …` for a tiktoken-accurate
116
+ * pre-debit if your caps are tight.
117
+ */
118
+ export declare function withBudget(opts: WithBudgetOptions): AiMiddleware;
119
+ //# sourceMappingURL=with-budget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with-budget.d.ts","sourceRoot":"","sources":["../../src/budget/with-budget.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,YAAY,EAEZ,iBAAiB,EAClB,MAAM,aAAa,CAAA;AACpB,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,cAAc,CAAA;AACrB,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACd,MAAM,cAAc,CAAA;AAErB;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAI,MAAM,GAAG,IAAI,CAAA;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,YAAY,CAAA;IACpB,gDAAgD;IAChD,KAAK,EAAG,MAAM,CAAA;IACd,GAAG,EAAK,MAAM,CAAA;IACd,GAAG,EAAK,iBAAiB,CAAA;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;;;OAMG;IACH,IAAI,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;IAE5F;;;;OAIG;IACH,MAAM,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,iBAAiB,CAAA;KAAE,GAAG,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;IAE1F;;;OAGG;IACH,OAAO,EAAE,aAAa,CAAA;IAEtB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IAEzC;;;;;;OAMG;IACH,UAAU,CAAC,CAAC,IAAI,EAAE,kBAAkB,GAAG,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;IAE7D;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;CAC1C;AAgBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,iBAAiB,GAAG,YAAY,CAmGhE"}
@@ -0,0 +1,175 @@
1
+ import { BudgetExceededError, ModelPricing, assertKnownModelPricing, } from './pricing.js';
2
+ const BudgetState = Symbol.for('rudderjs.ai.budget.state');
3
+ /**
4
+ * Per-user spend cap middleware (#A6 Phase 3).
5
+ *
6
+ * Pre-debits an input-cost estimate before each provider call (refusing
7
+ * with {@link BudgetExceededError} if the user would exceed any
8
+ * configured cap), then debits the actual cost difference once the
9
+ * `usage` chunk arrives.
10
+ *
11
+ * The pre-debit reserves budget so two concurrent requests can't both
12
+ * pass the check before either is billed — the `BudgetStorage` contract
13
+ * (#A6 Phase 2) requires `checkAndDebit` to be atomic.
14
+ *
15
+ * # Example
16
+ *
17
+ * ```ts
18
+ * import { withBudget, memoryBudgetStorage, ModelPricing } from '@rudderjs/ai'
19
+ *
20
+ * const budgeted = withBudget({
21
+ * user: (ctx) => ctx.context as string, // your app's user-id source
22
+ * budget: () => ({ daily: 0.50, monthly: 10 }), // USD
23
+ * storage: memoryBudgetStorage(),
24
+ * pricing: ModelPricing,
25
+ * })
26
+ *
27
+ * class MyAgent extends Agent {
28
+ * middleware() { return [budgeted] }
29
+ * }
30
+ * ```
31
+ *
32
+ * # Caveats
33
+ *
34
+ * - **Refunds on errors are not issued.** If the provider call fails
35
+ * after the pre-debit, the estimate stays debited. This avoids the
36
+ * complexity of distinguishing partial-credit cases (the model may
37
+ * have produced output before erroring). Apps that need refund-on-error
38
+ * should subscribe via `onError` and call `storage` directly.
39
+ * - **Cache token deltas not counted.** `TokenUsage` does not yet expose
40
+ * `cacheReadInputTokens` / `cacheWriteInputTokens`; cached requests are
41
+ * billed at the full `inputPer1k` rate today. Refining this is a phase
42
+ * 3.x follow-up that needs a `TokenUsage` widening.
43
+ * - **Tokenizer differences.** The default token estimator is
44
+ * `text.length / 4`. Provider-reported `usage.promptTokens` may differ
45
+ * by a few percent. Pass `estimateTokens: …` for a tiktoken-accurate
46
+ * pre-debit if your caps are tight.
47
+ */
48
+ export function withBudget(opts) {
49
+ const pricing = opts.pricing ?? ModelPricing;
50
+ const tz = opts.timezone;
51
+ const estimateTokens = opts.estimateTokens ?? defaultEstimateTokens;
52
+ const onExceeded = opts.onExceeded ?? defaultOnExceeded;
53
+ return {
54
+ name: 'budget',
55
+ async onIteration(ctx) {
56
+ // Pre-debit fires before each model call (every step), including
57
+ // step 1. `onIteration` runs after `onStart` but before
58
+ // `prepareStep`/`onConfig('beforeModel')` — so transforms applied by
59
+ // those later hooks (e.g. a `prepareStep` model swap) aren't
60
+ // reflected in this estimate. For v1 that's acceptable; tighten
61
+ // later if it bites.
62
+ const userId = await opts.user(ctx);
63
+ if (userId == null)
64
+ return; // bypass — unauthenticated path or admin
65
+ const modelKey = ctx.model;
66
+ // Fail loud if the model isn't priced — silently zero-costing through
67
+ // a typo'd model is the worst-of-both for budget enforcement.
68
+ const rate = assertKnownModelPricing(modelKey, pricing);
69
+ const caps = await opts.budget({ userId, ctx });
70
+ const definedCaps = [];
71
+ if (caps.daily != null)
72
+ definedCaps.push({ period: 'daily', cap: caps.daily });
73
+ if (caps.monthly != null)
74
+ definedCaps.push({ period: 'monthly', cap: caps.monthly });
75
+ if (definedCaps.length === 0)
76
+ return; // nothing to enforce
77
+ // Estimate input cost for THIS step from the live messages array.
78
+ const estimate = estimateInputCostUsd(ctx.messages, [], rate, estimateTokens);
79
+ // Pre-debit each defined period; throw on first denial.
80
+ for (const { period, cap } of definedCaps) {
81
+ const r = await opts.storage.checkAndDebit({
82
+ userId,
83
+ period,
84
+ cap,
85
+ costUsd: estimate,
86
+ ...(tz != null ? { timezone: tz } : {}),
87
+ });
88
+ if (!r.allowed) {
89
+ await onExceeded({ userId, period, spent: r.spent, cap: r.cap, ctx });
90
+ // If onExceeded didn't throw, fall back to the default error so
91
+ // the run is always aborted on a denied debit.
92
+ throw new BudgetExceededError({ userId, period, spent: r.spent, cap: r.cap });
93
+ }
94
+ }
95
+ // Stash for onUsage true-up. If estimate was high vs actual we just
96
+ // accept the small over-charge; if low, onUsage debits the delta.
97
+ ;
98
+ ctx[BudgetState] = {
99
+ userId,
100
+ caps: definedCaps,
101
+ pendingEstimate: estimate,
102
+ };
103
+ },
104
+ async onUsage(ctx, usage) {
105
+ const state = ctx[BudgetState];
106
+ if (!state)
107
+ return; // no user, was bypassed
108
+ const modelKey = ctx.model;
109
+ const rate = pricing[modelKey];
110
+ // If pricing was found at onConfig time it's still found here; this is
111
+ // belt-and-suspenders for the rare case where the agent loop swapped
112
+ // models mid-run (failover). Skip silently — the pre-debit covered
113
+ // estimate, so we under-charge actual on a missing-rate model.
114
+ if (!rate) {
115
+ state.pendingEstimate = 0;
116
+ return;
117
+ }
118
+ const actualCost = (usage.promptTokens * rate.inputPer1k +
119
+ usage.completionTokens * rate.outputPer1k) / 1000;
120
+ const delta = actualCost - state.pendingEstimate;
121
+ state.pendingEstimate = 0; // clear so a tool round-trip's next onConfig starts fresh
122
+ if (delta <= 0)
123
+ return; // overestimated; accept the small over-charge
124
+ // Always-apply true-up — the response already streamed, we can't
125
+ // unspend. Pass MAX_SAFE_INTEGER as cap so the storage just records
126
+ // the delta. Pre-debit enforcement already happened at onConfig.
127
+ for (const { period } of state.caps) {
128
+ await opts.storage.checkAndDebit({
129
+ userId: state.userId,
130
+ period,
131
+ cap: Number.MAX_SAFE_INTEGER,
132
+ costUsd: delta,
133
+ ...(tz != null ? { timezone: tz } : {}),
134
+ });
135
+ }
136
+ },
137
+ };
138
+ }
139
+ // ─── Helpers ──────────────────────────────────────────────
140
+ function defaultEstimateTokens(text) {
141
+ return Math.ceil(text.length / 4);
142
+ }
143
+ function defaultOnExceeded(args) {
144
+ throw new BudgetExceededError({
145
+ userId: args.userId,
146
+ period: args.period,
147
+ spent: args.spent,
148
+ cap: args.cap,
149
+ });
150
+ }
151
+ function estimateInputCostUsd(messages, systemPrompts, rate, estimateTokens) {
152
+ // Concatenating all input into one string for the estimator means a
153
+ // single tokenizer pass for tiktoken-backed estimators (cheaper than
154
+ // tokenizing each message separately and summing).
155
+ const parts = [...systemPrompts];
156
+ for (const m of messages)
157
+ parts.push(messageText(m));
158
+ const tokens = estimateTokens(parts.join('\n'));
159
+ return (tokens * rate.inputPer1k) / 1000;
160
+ }
161
+ function messageText(m) {
162
+ if (typeof m.content === 'string')
163
+ return m.content;
164
+ if (Array.isArray(m.content)) {
165
+ let s = '';
166
+ for (const part of m.content)
167
+ s += contentPartText(part);
168
+ return s;
169
+ }
170
+ return '';
171
+ }
172
+ function contentPartText(p) {
173
+ return p.type === 'text' ? p.text : '';
174
+ }
175
+ //# sourceMappingURL=with-budget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with-budget.js","sourceRoot":"","sources":["../../src/budget/with-budget.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * `@rudderjs/ai/budget-orm` — ORM-backed {@link BudgetStorage} for #A6 Phase 4.
3
+ *
4
+ * Production-grade replacement for `memoryBudgetStorage()` (which is
5
+ * single-process only). Persists per-user spend counters in a
6
+ * `BudgetUsage` table via the registered `@rudderjs/orm` adapter — works
7
+ * across queue workers, web processes, and horizontally-scaled deployments.
8
+ *
9
+ * Wire it into your AI middleware:
10
+ *
11
+ * ```ts
12
+ * import { withBudget } from '@rudderjs/ai'
13
+ * import { ormBudgetStorage } from '@rudderjs/ai/budget-orm'
14
+ *
15
+ * const budgeted = withBudget({
16
+ * user: (ctx) => ctx.context as string,
17
+ * budget: () => ({ daily: 0.50, monthly: 10 }),
18
+ * storage: ormBudgetStorage(),
19
+ * })
20
+ * ```
21
+ *
22
+ * The schema lives at {@link budgetUsagePrismaSchema} — copy it into your
23
+ * Prisma schema (or a new `prisma/schema/<file>.prisma` if you use the
24
+ * multi-file setup). The `@@unique([userId, period, periodKey])`
25
+ * constraint is the one load-bearing index — without it, the
26
+ * find-or-create path can race and produce duplicate rows.
27
+ *
28
+ * # Atomicity caveat
29
+ *
30
+ * `checkAndDebit` does a read-then-conditional-increment. The increment
31
+ * itself is atomic (`UPDATE col = col + n`), but the cap check sits
32
+ * between the read and the write. Under high concurrency for a single
33
+ * user (more than ~1 in-flight budgeted request at a time), total spend
34
+ * can briefly exceed `cap` by up to `costUsd × concurrency`. For typical
35
+ * apps this is a non-issue.
36
+ *
37
+ * Strict guarantees require a database transaction with serializable
38
+ * isolation or a Redis-backed counter — both planned as follow-ups. File
39
+ * an issue if you hit this in production.
40
+ */
41
+ import { Model } from '@rudderjs/orm';
42
+ import { type BudgetCheckOptions, type BudgetCheckResult, type BudgetPeriod, type BudgetStorage } from '../budget/storage.js';
43
+ /**
44
+ * Model row backing {@link OrmBudgetStorage}. Exposed so apps that
45
+ * want admin views (e.g. "show me top spenders this month") can use
46
+ * `BudgetUsageRecord.where(...).get()` instead of routing every read
47
+ * through the {@link BudgetStorage} interface.
48
+ *
49
+ * The `@@unique([userId, period, periodKey])` constraint is required —
50
+ * without it, two concurrent first-writes for the same user/period
51
+ * create duplicate rows and the cap accounting silently drifts.
52
+ */
53
+ export declare class BudgetUsageRecord extends Model {
54
+ static table: string;
55
+ static fillable: string[];
56
+ id: string;
57
+ userId: string;
58
+ /** `'daily'` or `'monthly'`. */
59
+ period: string;
60
+ /** TZ-aware bucket key — `YYYY-MM-DD` (daily) or `YYYY-MM` (monthly). */
61
+ periodKey: string;
62
+ /** Cumulative USD spend in this period. */
63
+ spent: number;
64
+ createdAt: Date;
65
+ updatedAt: Date | null;
66
+ }
67
+ /**
68
+ * Production `BudgetStorage` backed by the registered `@rudderjs/orm`
69
+ * adapter. See the module JSDoc for setup + the atomicity caveat.
70
+ */
71
+ export declare class OrmBudgetStorage implements BudgetStorage {
72
+ checkAndDebit(opts: BudgetCheckOptions): Promise<BudgetCheckResult>;
73
+ /** Apply the read-then-conditional-increment path on an existing row. */
74
+ private _applyIncrementPath;
75
+ reset(userId: string, period: BudgetPeriod, now?: Date, timezone?: string): Promise<void>;
76
+ }
77
+ /**
78
+ * Convenience factory — returns a fresh {@link OrmBudgetStorage}
79
+ * instance. Prefer this over `new OrmBudgetStorage()` for symmetry with
80
+ * `memoryBudgetStorage()`.
81
+ */
82
+ export declare function ormBudgetStorage(): BudgetStorage;
83
+ /**
84
+ * Reference Prisma schema for `OrmBudgetStorage`. Copy into your
85
+ * `prisma/schema/<file>.prisma` (or paste alongside an existing model).
86
+ *
87
+ * The `@@unique([userId, period, periodKey])` constraint is required —
88
+ * without it the find-or-create path can race and produce duplicate
89
+ * rows, breaking cap accounting.
90
+ *
91
+ * SQLite stores `Float` as `REAL`; Postgres / MySQL as `DOUBLE
92
+ * PRECISION` / `DOUBLE`. All three give 15+ significant digits — more
93
+ * than enough for sub-cent budget tracking.
94
+ */
95
+ export declare const budgetUsagePrismaSchema = "model BudgetUsage {\n id String @id @default(cuid())\n userId String\n /// 'daily' | 'monthly'\n period String\n /// YYYY-MM-DD (daily) or YYYY-MM (monthly), in the configured timezone\n periodKey String\n /// Cumulative USD spend in this period\n spent Float @default(0)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@unique([userId, period, periodKey])\n @@index([userId])\n}\n";
96
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/budget-orm/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AACrC,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,aAAa,EAEnB,MAAM,sBAAsB,CAAA;AAI7B;;;;;;;;;GASG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,OAAgB,KAAK,SAAmB;IACxC,OAAgB,QAAQ,WAA6C;IAE7D,EAAE,EAAS,MAAM,CAAA;IACjB,MAAM,EAAK,MAAM,CAAA;IACzB,gCAAgC;IACxB,MAAM,EAAK,MAAM,CAAA;IACzB,yEAAyE;IACjE,SAAS,EAAE,MAAM,CAAA;IACzB,2CAA2C;IACnC,KAAK,EAAM,MAAM,CAAA;IACjB,SAAS,EAAE,IAAI,CAAA;IACf,SAAS,EAAE,IAAI,GAAG,IAAI,CAAA;CAC/B;AAID;;;GAGG;AACH,qBAAa,gBAAiB,YAAW,aAAa;IAC9C,aAAa,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAuDzE,yEAAyE;YAC3D,mBAAmB;IAsB3B,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAQhG;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,aAAa,CAEhD;AAID;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,uBAAuB,8bAenC,CAAA"}