@rudderjs/ai 1.5.0 → 1.6.1

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 (163) hide show
  1. package/README.md +482 -4
  2. package/boost/guidelines.md +60 -0
  3. package/boost/skills/ai-agents/SKILL.md +7 -0
  4. package/boost/skills/ai-tools/SKILL.md +7 -0
  5. package/dist/agent.d.ts +35 -1
  6. package/dist/agent.d.ts.map +1 -1
  7. package/dist/agent.js +118 -16
  8. package/dist/agent.js.map +1 -1
  9. package/dist/budget/pricing.d.ts +124 -0
  10. package/dist/budget/pricing.d.ts.map +1 -0
  11. package/dist/budget/pricing.js +175 -0
  12. package/dist/budget/pricing.js.map +1 -0
  13. package/dist/budget/storage.d.ts +104 -0
  14. package/dist/budget/storage.d.ts.map +1 -0
  15. package/dist/budget/storage.js +0 -0
  16. package/dist/budget/storage.js.map +1 -0
  17. package/dist/budget/with-budget.d.ts +119 -0
  18. package/dist/budget/with-budget.d.ts.map +1 -0
  19. package/dist/budget/with-budget.js +175 -0
  20. package/dist/budget/with-budget.js.map +1 -0
  21. package/dist/budget-orm/index.d.ts +96 -0
  22. package/dist/budget-orm/index.d.ts.map +1 -0
  23. package/dist/budget-orm/index.js +177 -0
  24. package/dist/budget-orm/index.js.map +1 -0
  25. package/dist/commands/ai-eval.d.ts +93 -0
  26. package/dist/commands/ai-eval.d.ts.map +1 -0
  27. package/dist/commands/ai-eval.js +378 -0
  28. package/dist/commands/ai-eval.js.map +1 -0
  29. package/dist/computer-use/actions.d.ts +214 -0
  30. package/dist/computer-use/actions.d.ts.map +1 -0
  31. package/dist/computer-use/actions.js +48 -0
  32. package/dist/computer-use/actions.js.map +1 -0
  33. package/dist/computer-use/errors.d.ts +57 -0
  34. package/dist/computer-use/errors.d.ts.map +1 -0
  35. package/dist/computer-use/errors.js +76 -0
  36. package/dist/computer-use/errors.js.map +1 -0
  37. package/dist/computer-use/index.d.ts +53 -0
  38. package/dist/computer-use/index.d.ts.map +1 -0
  39. package/dist/computer-use/index.js +51 -0
  40. package/dist/computer-use/index.js.map +1 -0
  41. package/dist/computer-use/playwright.d.ts +76 -0
  42. package/dist/computer-use/playwright.d.ts.map +1 -0
  43. package/dist/computer-use/playwright.js +270 -0
  44. package/dist/computer-use/playwright.js.map +1 -0
  45. package/dist/computer-use/tool.d.ts +154 -0
  46. package/dist/computer-use/tool.d.ts.map +1 -0
  47. package/dist/computer-use/tool.js +210 -0
  48. package/dist/computer-use/tool.js.map +1 -0
  49. package/dist/eval/fixtures.d.ts +65 -0
  50. package/dist/eval/fixtures.d.ts.map +1 -0
  51. package/dist/eval/fixtures.js +110 -0
  52. package/dist/eval/fixtures.js.map +1 -0
  53. package/dist/eval/html-reporter.d.ts +25 -0
  54. package/dist/eval/html-reporter.d.ts.map +1 -0
  55. package/dist/eval/html-reporter.js +209 -0
  56. package/dist/eval/html-reporter.js.map +1 -0
  57. package/dist/eval/index.d.ts +271 -0
  58. package/dist/eval/index.d.ts.map +1 -0
  59. package/dist/eval/index.js +510 -0
  60. package/dist/eval/index.js.map +1 -0
  61. package/dist/eval/json-reporter.d.ts +43 -0
  62. package/dist/eval/json-reporter.d.ts.map +1 -0
  63. package/dist/eval/json-reporter.js +40 -0
  64. package/dist/eval/json-reporter.js.map +1 -0
  65. package/dist/fake.d.ts +36 -1
  66. package/dist/fake.d.ts.map +1 -1
  67. package/dist/fake.js +49 -2
  68. package/dist/fake.js.map +1 -1
  69. package/dist/file-search.d.ts +168 -0
  70. package/dist/file-search.d.ts.map +1 -0
  71. package/dist/file-search.js +158 -0
  72. package/dist/file-search.js.map +1 -0
  73. package/dist/index.d.ts +22 -2
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +17 -1
  76. package/dist/index.js.map +1 -1
  77. package/dist/mcp/client-tools.d.ts +39 -0
  78. package/dist/mcp/client-tools.d.ts.map +1 -0
  79. package/dist/mcp/client-tools.js +147 -0
  80. package/dist/mcp/client-tools.js.map +1 -0
  81. package/dist/mcp/index.d.ts +16 -0
  82. package/dist/mcp/index.d.ts.map +1 -0
  83. package/dist/mcp/index.js +15 -0
  84. package/dist/mcp/index.js.map +1 -0
  85. package/dist/mcp/server-from-agent.d.ts +24 -0
  86. package/dist/mcp/server-from-agent.d.ts.map +1 -0
  87. package/dist/mcp/server-from-agent.js +113 -0
  88. package/dist/mcp/server-from-agent.js.map +1 -0
  89. package/dist/mcp/types.d.ts +64 -0
  90. package/dist/mcp/types.d.ts.map +1 -0
  91. package/dist/mcp/types.js +6 -0
  92. package/dist/mcp/types.js.map +1 -0
  93. package/dist/memory-embedding/index.d.ts +121 -0
  94. package/dist/memory-embedding/index.d.ts.map +1 -0
  95. package/dist/memory-embedding/index.js +229 -0
  96. package/dist/memory-embedding/index.js.map +1 -0
  97. package/dist/memory-extract.d.ts +60 -0
  98. package/dist/memory-extract.d.ts.map +1 -0
  99. package/dist/memory-extract.js +163 -0
  100. package/dist/memory-extract.js.map +1 -0
  101. package/dist/memory-inject.d.ts +39 -0
  102. package/dist/memory-inject.d.ts.map +1 -0
  103. package/dist/memory-inject.js +135 -0
  104. package/dist/memory-inject.js.map +1 -0
  105. package/dist/memory-orm/index.d.ts +118 -0
  106. package/dist/memory-orm/index.d.ts.map +1 -0
  107. package/dist/memory-orm/index.js +187 -0
  108. package/dist/memory-orm/index.js.map +1 -0
  109. package/dist/memory.d.ts +55 -0
  110. package/dist/memory.d.ts.map +1 -0
  111. package/dist/memory.js +132 -0
  112. package/dist/memory.js.map +1 -0
  113. package/dist/observers.d.ts +22 -0
  114. package/dist/observers.d.ts.map +1 -1
  115. package/dist/observers.js.map +1 -1
  116. package/dist/provider-tools.d.ts +15 -1
  117. package/dist/provider-tools.d.ts.map +1 -1
  118. package/dist/provider-tools.js +21 -1
  119. package/dist/provider-tools.js.map +1 -1
  120. package/dist/providers/anthropic.d.ts.map +1 -1
  121. package/dist/providers/anthropic.js +61 -6
  122. package/dist/providers/anthropic.js.map +1 -1
  123. package/dist/providers/elevenlabs.d.ts +98 -0
  124. package/dist/providers/elevenlabs.d.ts.map +1 -0
  125. package/dist/providers/elevenlabs.js +229 -0
  126. package/dist/providers/elevenlabs.js.map +1 -0
  127. package/dist/providers/google.d.ts +83 -1
  128. package/dist/providers/google.d.ts.map +1 -1
  129. package/dist/providers/google.js +491 -8
  130. package/dist/providers/google.js.map +1 -1
  131. package/dist/providers/openai.d.ts +3 -1
  132. package/dist/providers/openai.d.ts.map +1 -1
  133. package/dist/providers/openai.js +209 -5
  134. package/dist/providers/openai.js.map +1 -1
  135. package/dist/providers/voyage.d.ts +91 -0
  136. package/dist/providers/voyage.d.ts.map +1 -0
  137. package/dist/providers/voyage.js +166 -0
  138. package/dist/providers/voyage.js.map +1 -0
  139. package/dist/queue-job.d.ts +69 -4
  140. package/dist/queue-job.d.ts.map +1 -1
  141. package/dist/queue-job.js +114 -11
  142. package/dist/queue-job.js.map +1 -1
  143. package/dist/registry.d.ts +3 -1
  144. package/dist/registry.d.ts.map +1 -1
  145. package/dist/registry.js +10 -0
  146. package/dist/registry.js.map +1 -1
  147. package/dist/server/provider.d.ts.map +1 -1
  148. package/dist/server/provider.js +23 -1
  149. package/dist/server/provider.js.map +1 -1
  150. package/dist/similarity-search.d.ts +163 -0
  151. package/dist/similarity-search.d.ts.map +1 -0
  152. package/dist/similarity-search.js +147 -0
  153. package/dist/similarity-search.js.map +1 -0
  154. package/dist/tool.d.ts.map +1 -1
  155. package/dist/tool.js +13 -4
  156. package/dist/tool.js.map +1 -1
  157. package/dist/types.d.ts +246 -0
  158. package/dist/types.d.ts.map +1 -1
  159. package/dist/vector-stores/index.d.ts +96 -0
  160. package/dist/vector-stores/index.d.ts.map +1 -0
  161. package/dist/vector-stores/index.js +153 -0
  162. package/dist/vector-stores/index.js.map +1 -0
  163. package/package.json +41 -3
@@ -0,0 +1,135 @@
1
+ import { resolveUserMemory } from './agent.js';
2
+ /**
3
+ * Pre-prompt {@link AiMiddleware} that consults a {@link UserMemory}, picks
4
+ * facts relevant to the latest user input, and prepends them to the
5
+ * agent's system message as a fenced `<user-memory>…</user-memory>`
6
+ * block. Auto-installed by `Agent.prompt` / `Agent.stream` when
7
+ * `Agent.remembers()` returns `{ inject: 'auto', … }`; can also be
8
+ * dropped into `Agent.middleware()` manually.
9
+ *
10
+ * Runs in `onStart` (async) — `onConfig` is sync and `recall()` is not.
11
+ * Mutates `ctx.messages[0]` (the system message) in place; the agent
12
+ * loop's `messages` array is the same reference, so the model sees the
13
+ * augmented prompt on the very next provider call.
14
+ *
15
+ * Skips silently when:
16
+ * - no `UserMemory` is registered (lookup returns `undefined`)
17
+ * - no user message exists in `ctx.messages` (continuation flow where
18
+ * the trailing message is `tool` / `assistant`)
19
+ * - `recall()` returns no facts
20
+ * - the rendered block doesn't fit even one fact under
21
+ * `spec.injectTokenBudget`
22
+ */
23
+ export function withMemoryInject(spec, opts = {}) {
24
+ const lookup = opts.lookup ?? resolveUserMemory;
25
+ const estimate = opts.estimateTokens ?? defaultEstimateTokens;
26
+ return {
27
+ name: 'memory-inject',
28
+ async onStart(ctx) {
29
+ const userText = findLatestUserText(ctx.messages);
30
+ if (!userText)
31
+ return;
32
+ const mem = lookup();
33
+ if (!mem)
34
+ return;
35
+ const recallOpts = {};
36
+ if (spec.injectLimit !== undefined)
37
+ recallOpts.limit = spec.injectLimit;
38
+ if (spec.tags !== undefined)
39
+ recallOpts.tags = spec.tags;
40
+ const facts = await mem.recall(spec.user, userText, recallOpts);
41
+ if (facts.length === 0)
42
+ return;
43
+ const trimmed = applyTokenBudget(facts, spec.injectTokenBudget, estimate);
44
+ if (trimmed.length === 0)
45
+ return;
46
+ const block = renderMemoryBlock(trimmed);
47
+ const sys = ctx.messages[0];
48
+ if (!sys || sys.role !== 'system')
49
+ return;
50
+ const original = systemContentToString(sys);
51
+ ctx.messages[0] = { role: 'system', content: `${original}\n\n${block}` };
52
+ },
53
+ };
54
+ }
55
+ // ─── Helpers ──────────────────────────────────────────────
56
+ function defaultEstimateTokens(text) {
57
+ // ~4 chars/token works for English; gpt-tokenizer adds a runtime dep
58
+ // we don't want to make mandatory just for a budget approximation.
59
+ return Math.ceil(text.length / 4);
60
+ }
61
+ /**
62
+ * Walk `messages` from the end and return the text of the most recent
63
+ * `role: 'user'` entry. Returns `null` when:
64
+ * - no user message exists (the loop is in a continuation flow where
65
+ * `options.messages` was passed and ends with `tool`/`assistant`), or
66
+ * - the user message has only non-text content parts.
67
+ *
68
+ * The rationale for "latest user" rather than "all user history": the
69
+ * search query that maps best to recall accuracy is the user's current
70
+ * request. Earlier turns already shaped the loaded conversation
71
+ * history, which the persistence layer handles separately.
72
+ */
73
+ function findLatestUserText(messages) {
74
+ for (let i = messages.length - 1; i >= 0; i--) {
75
+ const m = messages[i];
76
+ if (m && m.role === 'user') {
77
+ const text = userContentToString(m);
78
+ return text.length > 0 ? text : null;
79
+ }
80
+ }
81
+ return null;
82
+ }
83
+ function userContentToString(m) {
84
+ if (typeof m.content === 'string')
85
+ return m.content;
86
+ return contentPartsToString(m.content);
87
+ }
88
+ function systemContentToString(m) {
89
+ if (typeof m.content === 'string')
90
+ return m.content;
91
+ return contentPartsToString(m.content);
92
+ }
93
+ function contentPartsToString(parts) {
94
+ const out = [];
95
+ for (const p of parts) {
96
+ if (p.type === 'text' && typeof p.text === 'string')
97
+ out.push(p.text);
98
+ }
99
+ return out.join('\n');
100
+ }
101
+ /**
102
+ * If `budget` is set, sort facts by score descending (undefined scores
103
+ * treated as 0.5 — neutral) and accumulate until the rendered block's
104
+ * estimated tokens would exceed `budget`. Returns the accepted prefix.
105
+ *
106
+ * If `budget` is unset, returns `facts` unchanged.
107
+ */
108
+ function applyTokenBudget(facts, budget, estimate) {
109
+ if (budget === undefined || budget <= 0)
110
+ return facts;
111
+ // Sort by score desc; undefined → 0.5 so user-asserted facts (no
112
+ // score) tie with mid-confidence model-extracted facts.
113
+ const sorted = [...facts].sort((a, b) => (b.score ?? 0.5) - (a.score ?? 0.5));
114
+ // Render incrementally to account for the wrapper overhead.
115
+ const accepted = [];
116
+ for (const f of sorted) {
117
+ const candidate = renderMemoryBlock([...accepted, f]);
118
+ if (estimate(candidate) > budget)
119
+ break;
120
+ accepted.push(f);
121
+ }
122
+ return accepted;
123
+ }
124
+ /**
125
+ * Render the prepended block. The fenced `<user-memory>` tag gives
126
+ * downstream systems (telescope, evals, screenshots) a stable hook to
127
+ * detect / strip injected memory, and signals the model that the
128
+ * content is provided by the framework rather than written into the
129
+ * agent's instructions.
130
+ */
131
+ function renderMemoryBlock(facts) {
132
+ const lines = facts.map(f => `- ${f.fact}`);
133
+ return `<user-memory>\n${lines.join('\n')}\n</user-memory>`;
134
+ }
135
+ //# sourceMappingURL=memory-inject.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-inject.js","sourceRoot":"","sources":["../src/memory-inject.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * `@rudderjs/ai/memory-orm` — ORM-backed {@link UserMemory} for #A4 Phase 4.
3
+ *
4
+ * Stores per-user facts in a `UserMemory` table via the registered
5
+ * `@rudderjs/orm` adapter (Prisma today; Drizzle as well once the user's
6
+ * tables are wired). Drop-in alongside Phase 1's in-process
7
+ * `MemoryUserMemory`.
8
+ *
9
+ * Wire it from your AI config:
10
+ *
11
+ * ```ts
12
+ * // config/ai.ts
13
+ * import type { AiConfig } from '@rudderjs/ai'
14
+ * import { OrmUserMemory } from '@rudderjs/ai/memory-orm'
15
+ *
16
+ * export default {
17
+ * default: 'anthropic/claude-sonnet-4-5',
18
+ * providers: { ... },
19
+ * memory: new OrmUserMemory(),
20
+ * } satisfies AiConfig
21
+ * ```
22
+ *
23
+ * The schema lives at `@rudderjs/ai/memory-orm`'s {@link userMemoryPrismaSchema}
24
+ * — copy it into your Prisma schema. The optional `embedding Bytes?`
25
+ * column is shipped here in Phase 4 (intentionally nullable) so Phase 5's
26
+ * `EmbeddingUserMemory` can populate it without forcing an additive
27
+ * migration.
28
+ */
29
+ import { Model } from '@rudderjs/orm';
30
+ import type { MemoryEntry, UserMemory } from '../types.js';
31
+ /**
32
+ * The Model row backing {@link OrmUserMemory}. Exposed so apps that
33
+ * want their own queries (admin views, audit dumps) can use the
34
+ * familiar `UserMemoryRecord.where(...).get()` instead of routing
35
+ * everything through the {@link UserMemory} interface.
36
+ *
37
+ * Tags persist as a JSON-encoded string in the `tags` column — both
38
+ * Prisma's portable `String?` and Drizzle's `text` work without
39
+ * needing native array columns. The {@link UserMemory.recall} path
40
+ * filters tags in JavaScript for the same reason.
41
+ *
42
+ * The `embedding Bytes?` column is in the schema as of Phase 4
43
+ * (nullable) so `@rudderjs/ai/memory-embedding`'s `EmbeddingUserMemory`
44
+ * (Phase 5) writes the Float32-packed vector here on `remember()` and
45
+ * reads it for cosine recall. `OrmUserMemory` ignores it — the
46
+ * column stays `null` for any row stored without the embedding
47
+ * composer.
48
+ */
49
+ export declare class UserMemoryRecord extends Model {
50
+ static table: string;
51
+ static fillable: string[];
52
+ id: string;
53
+ userId: string;
54
+ fact: string;
55
+ /** JSON-encoded `string[]` or null. Use `getTags()` for the parsed shape. */
56
+ tags: string | null;
57
+ score: number | null;
58
+ /**
59
+ * Float32-packed vector serialized via
60
+ * `@rudderjs/ai/memory-embedding`'s `serializeVector` /
61
+ * `deserializeVector`. `null` when the row was stored without the
62
+ * embedding composer (Phase 4-only setups).
63
+ */
64
+ embedding: Uint8Array | null;
65
+ createdAt: Date;
66
+ updatedAt: Date | null;
67
+ /** Parsed tags array; empty when nothing was stored. */
68
+ getTags(): string[];
69
+ }
70
+ /**
71
+ * `UserMemory` implementation that persists rows to the registered
72
+ * ORM adapter. Designed for production use — the in-process
73
+ * `MemoryUserMemory` is for tests and dev.
74
+ *
75
+ * Adapter coverage:
76
+ * - Prisma — works out of the box; copy {@link userMemoryPrismaSchema}
77
+ * into your schema.
78
+ * - Drizzle — works once you define a table matching the schema's
79
+ * columns and register it via `tables: { userMemory: <table> }` on
80
+ * the `drizzle()` config.
81
+ *
82
+ * Recall semantics: case-insensitive **token-OR-LIKE** matching against
83
+ * the `fact` column. The query is tokenized on non-alphanumeric
84
+ * boundaries (≥3-char tokens) and any row whose `fact` matches at
85
+ * least one token via `LIKE %tok%` is returned. Mirrors Phase 1's
86
+ * `MemoryUserMemory.recall()` behavior so the two backends are
87
+ * swap-compatible. Tag scope is applied JS-side after fetch — pushing
88
+ * tag-array filtering into the WHERE is adapter-specific and lands in a
89
+ * follow-up.
90
+ */
91
+ export declare class OrmUserMemory implements UserMemory {
92
+ remember(userId: string, fact: string, opts?: {
93
+ tags?: string[];
94
+ score?: number;
95
+ }): Promise<MemoryEntry>;
96
+ recall(userId: string, query: string, opts?: {
97
+ limit?: number;
98
+ tags?: string[];
99
+ }): Promise<MemoryEntry[]>;
100
+ forget(userId: string, factId: string): Promise<void>;
101
+ list(userId: string, opts?: {
102
+ tags?: string[];
103
+ limit?: number;
104
+ }): Promise<MemoryEntry[]>;
105
+ forgetAll(userId: string): Promise<void>;
106
+ }
107
+ /**
108
+ * Reference Prisma schema for `OrmUserMemory`. Copy into your
109
+ * `prisma/schema/<file>.prisma` (or paste alongside an existing
110
+ * model). The `embedding Bytes?` column is intentionally nullable so
111
+ * Phase 5's `EmbeddingUserMemory` becomes additive — no schema
112
+ * migration when you upgrade.
113
+ *
114
+ * SQLite stores `Bytes` as `BLOB`; Postgres stores it as `bytea`.
115
+ * Both work for the dot-product implementation Phase 5 will use.
116
+ */
117
+ export declare const userMemoryPrismaSchema = "model UserMemory {\n id String @id @default(cuid())\n userId String\n fact String\n /// JSON-encoded `string[]` of tags, or null\n tags String?\n /// Confidence score in [0, 1] \u2014 extract sets this from the model's self-rating\n score Float?\n /// Phase 5 \u2014 vector embedding for cosine recall (nullable so Phase 4 ignores it)\n embedding Bytes?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([userId])\n}\n";
118
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/memory-orm/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AACrC,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACX,MAAM,aAAa,CAAA;AAIpB;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,OAAgB,KAAK,SAAe;IAEpC,OAAgB,QAAQ,WAAmD;IAEnE,EAAE,EAAS,MAAM,CAAA;IACjB,MAAM,EAAK,MAAM,CAAA;IACjB,IAAI,EAAO,MAAM,CAAA;IACzB,6EAA6E;IACrE,IAAI,EAAO,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,EAAM,MAAM,GAAG,IAAI,CAAA;IAChC;;;;;OAKG;IACK,SAAS,EAAE,UAAU,GAAG,IAAI,CAAA;IAC5B,SAAS,EAAE,IAAI,CAAA;IACf,SAAS,EAAE,IAAI,GAAG,IAAI,CAAA;IAE9B,wDAAwD;IACxD,OAAO,IAAI,MAAM,EAAE;CASpB;AAID;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,aAAc,YAAW,UAAU;IACxC,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAI,MAAM,EACd,IAAI,CAAC,EAAG;QAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC1C,OAAO,CAAC,WAAW,CAAC;IASjB,MAAM,CACV,MAAM,EAAE,MAAM,EACd,KAAK,EAAG,MAAM,EACd,IAAI,CAAC,EAAG;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAC1C,OAAO,CAAC,WAAW,EAAE,CAAC;IAgBnB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrD,IAAI,CACR,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAG;QAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC1C,OAAO,CAAC,WAAW,EAAE,CAAC;IAMnB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG/C;AAID;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,4eAelC,CAAA"}
@@ -0,0 +1,187 @@
1
+ /**
2
+ * `@rudderjs/ai/memory-orm` — ORM-backed {@link UserMemory} for #A4 Phase 4.
3
+ *
4
+ * Stores per-user facts in a `UserMemory` table via the registered
5
+ * `@rudderjs/orm` adapter (Prisma today; Drizzle as well once the user's
6
+ * tables are wired). Drop-in alongside Phase 1's in-process
7
+ * `MemoryUserMemory`.
8
+ *
9
+ * Wire it from your AI config:
10
+ *
11
+ * ```ts
12
+ * // config/ai.ts
13
+ * import type { AiConfig } from '@rudderjs/ai'
14
+ * import { OrmUserMemory } from '@rudderjs/ai/memory-orm'
15
+ *
16
+ * export default {
17
+ * default: 'anthropic/claude-sonnet-4-5',
18
+ * providers: { ... },
19
+ * memory: new OrmUserMemory(),
20
+ * } satisfies AiConfig
21
+ * ```
22
+ *
23
+ * The schema lives at `@rudderjs/ai/memory-orm`'s {@link userMemoryPrismaSchema}
24
+ * — copy it into your Prisma schema. The optional `embedding Bytes?`
25
+ * column is shipped here in Phase 4 (intentionally nullable) so Phase 5's
26
+ * `EmbeddingUserMemory` can populate it without forcing an additive
27
+ * migration.
28
+ */
29
+ import { Model } from '@rudderjs/orm';
30
+ // ─── ORM Model ────────────────────────────────────────────
31
+ /**
32
+ * The Model row backing {@link OrmUserMemory}. Exposed so apps that
33
+ * want their own queries (admin views, audit dumps) can use the
34
+ * familiar `UserMemoryRecord.where(...).get()` instead of routing
35
+ * everything through the {@link UserMemory} interface.
36
+ *
37
+ * Tags persist as a JSON-encoded string in the `tags` column — both
38
+ * Prisma's portable `String?` and Drizzle's `text` work without
39
+ * needing native array columns. The {@link UserMemory.recall} path
40
+ * filters tags in JavaScript for the same reason.
41
+ *
42
+ * The `embedding Bytes?` column is in the schema as of Phase 4
43
+ * (nullable) so `@rudderjs/ai/memory-embedding`'s `EmbeddingUserMemory`
44
+ * (Phase 5) writes the Float32-packed vector here on `remember()` and
45
+ * reads it for cosine recall. `OrmUserMemory` ignores it — the
46
+ * column stays `null` for any row stored without the embedding
47
+ * composer.
48
+ */
49
+ export class UserMemoryRecord extends Model {
50
+ static table = 'userMemory';
51
+ static fillable = ['userId', 'fact', 'tags', 'score', 'embedding'];
52
+ /** Parsed tags array; empty when nothing was stored. */
53
+ getTags() {
54
+ if (this.tags == null || this.tags === '')
55
+ return [];
56
+ try {
57
+ const parsed = JSON.parse(this.tags);
58
+ return Array.isArray(parsed) ? parsed.filter(t => typeof t === 'string') : [];
59
+ }
60
+ catch {
61
+ return [];
62
+ }
63
+ }
64
+ }
65
+ // ─── UserMemory adapter ───────────────────────────────────
66
+ /**
67
+ * `UserMemory` implementation that persists rows to the registered
68
+ * ORM adapter. Designed for production use — the in-process
69
+ * `MemoryUserMemory` is for tests and dev.
70
+ *
71
+ * Adapter coverage:
72
+ * - Prisma — works out of the box; copy {@link userMemoryPrismaSchema}
73
+ * into your schema.
74
+ * - Drizzle — works once you define a table matching the schema's
75
+ * columns and register it via `tables: { userMemory: <table> }` on
76
+ * the `drizzle()` config.
77
+ *
78
+ * Recall semantics: case-insensitive **token-OR-LIKE** matching against
79
+ * the `fact` column. The query is tokenized on non-alphanumeric
80
+ * boundaries (≥3-char tokens) and any row whose `fact` matches at
81
+ * least one token via `LIKE %tok%` is returned. Mirrors Phase 1's
82
+ * `MemoryUserMemory.recall()` behavior so the two backends are
83
+ * swap-compatible. Tag scope is applied JS-side after fetch — pushing
84
+ * tag-array filtering into the WHERE is adapter-specific and lands in a
85
+ * follow-up.
86
+ */
87
+ export class OrmUserMemory {
88
+ async remember(userId, fact, opts) {
89
+ const data = { userId, fact };
90
+ if (opts?.tags !== undefined)
91
+ data['tags'] = JSON.stringify(opts.tags);
92
+ if (opts?.score !== undefined)
93
+ data['score'] = opts.score;
94
+ const created = await UserMemoryRecord.create(data);
95
+ return rowToEntry(created);
96
+ }
97
+ async recall(userId, query, opts) {
98
+ const tokens = tokenize(query);
99
+ let q = UserMemoryRecord.where('userId', userId);
100
+ if (tokens.size > 0) {
101
+ const tokenList = [...tokens];
102
+ q = q.whereGroup(g => {
103
+ for (const tok of tokenList)
104
+ g.orWhere('fact', 'LIKE', `%${tok}%`);
105
+ });
106
+ }
107
+ const rows = await q.orderBy('createdAt', 'ASC').get();
108
+ const entries = rows.map(rowToEntry).filter(e => matchesTags(e, opts?.tags));
109
+ return capLimit(entries, opts?.limit);
110
+ }
111
+ async forget(userId, factId) {
112
+ const row = await UserMemoryRecord.where('id', factId).where('userId', userId).first();
113
+ if (row)
114
+ await row.delete();
115
+ }
116
+ async list(userId, opts) {
117
+ const rows = await UserMemoryRecord.where('userId', userId).orderBy('createdAt', 'ASC').get();
118
+ const entries = rows.map(rowToEntry).filter(e => matchesTags(e, opts?.tags));
119
+ return capLimit(entries, opts?.limit);
120
+ }
121
+ async forgetAll(userId) {
122
+ await UserMemoryRecord.where('userId', userId).deleteAll();
123
+ }
124
+ }
125
+ // ─── Schema reference ─────────────────────────────────────
126
+ /**
127
+ * Reference Prisma schema for `OrmUserMemory`. Copy into your
128
+ * `prisma/schema/<file>.prisma` (or paste alongside an existing
129
+ * model). The `embedding Bytes?` column is intentionally nullable so
130
+ * Phase 5's `EmbeddingUserMemory` becomes additive — no schema
131
+ * migration when you upgrade.
132
+ *
133
+ * SQLite stores `Bytes` as `BLOB`; Postgres stores it as `bytea`.
134
+ * Both work for the dot-product implementation Phase 5 will use.
135
+ */
136
+ export const userMemoryPrismaSchema = `model UserMemory {
137
+ id String @id @default(cuid())
138
+ userId String
139
+ fact String
140
+ /// JSON-encoded \`string[]\` of tags, or null
141
+ tags String?
142
+ /// Confidence score in [0, 1] — extract sets this from the model's self-rating
143
+ score Float?
144
+ /// Phase 5 — vector embedding for cosine recall (nullable so Phase 4 ignores it)
145
+ embedding Bytes?
146
+ createdAt DateTime @default(now())
147
+ updatedAt DateTime @updatedAt
148
+
149
+ @@index([userId])
150
+ }
151
+ `;
152
+ // ─── Helpers ──────────────────────────────────────────────
153
+ function rowToEntry(row) {
154
+ const tags = row.getTags();
155
+ const out = {
156
+ id: row.id,
157
+ userId: row.userId,
158
+ fact: row.fact,
159
+ createdAt: row.createdAt,
160
+ };
161
+ if (tags.length > 0)
162
+ out.tags = tags;
163
+ if (row.score != null)
164
+ out.score = row.score;
165
+ if (row.updatedAt != null)
166
+ out.updatedAt = row.updatedAt;
167
+ return out;
168
+ }
169
+ function tokenize(s) {
170
+ const out = new Set();
171
+ for (const tok of s.toLowerCase().split(/[^a-z0-9]+/)) {
172
+ if (tok.length >= 3)
173
+ out.add(tok);
174
+ }
175
+ return out;
176
+ }
177
+ function matchesTags(entry, wanted) {
178
+ if (!wanted || wanted.length === 0)
179
+ return true;
180
+ if (!entry.tags || entry.tags.length === 0)
181
+ return false;
182
+ return wanted.every(t => entry.tags.includes(t));
183
+ }
184
+ function capLimit(items, limit) {
185
+ return limit !== undefined && limit > 0 ? items.slice(0, limit) : items;
186
+ }
187
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/memory-orm/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import type { MemoryEntry, RemembersOverride, RemembersSpec, UserMemory } from './types.js';
2
+ /**
3
+ * In-process, Map-backed {@link UserMemory}. Ships in the runtime-agnostic
4
+ * main entry — same pattern as {@link MemoryConversationStore}. Suitable
5
+ * for tests and dev; production apps configure an ORM- or
6
+ * embedding-backed store via `AiConfig.memory`.
7
+ *
8
+ * `recall()` uses case-insensitive **token-overlap** matching against
9
+ * `fact + tags`: the query is tokenized on non-alphanumeric boundaries
10
+ * and any fact sharing at least one token (≥3 chars) is returned. This
11
+ * lets natural-language queries like "what is my project?" pull facts
12
+ * containing "project" without forcing the caller to extract keywords.
13
+ * Matches return in insertion order with no scoring (binary yes/no).
14
+ * Tag filters apply before the token check — they intersect with the
15
+ * entry's own tags.
16
+ */
17
+ export declare class MemoryUserMemory implements UserMemory {
18
+ private readonly entries;
19
+ remember(userId: string, fact: string, opts?: {
20
+ tags?: string[];
21
+ score?: number;
22
+ }): Promise<MemoryEntry>;
23
+ recall(userId: string, query: string, opts?: {
24
+ limit?: number;
25
+ tags?: string[];
26
+ }): Promise<MemoryEntry[]>;
27
+ forget(userId: string, factId: string): Promise<void>;
28
+ list(userId: string, opts?: {
29
+ tags?: string[];
30
+ limit?: number;
31
+ }): Promise<MemoryEntry[]>;
32
+ forgetAll(userId: string): Promise<void>;
33
+ private allForUser;
34
+ }
35
+ /**
36
+ * Resolves the effective {@link RemembersSpec} for a single
37
+ * `prompt()` / `stream()` call. Returns `null` when memory should be
38
+ * skipped for this call.
39
+ *
40
+ * Precedence (high → low):
41
+ * 1. Per-call `options.memory` — `false` opts out, a spec replaces the
42
+ * agent's declaration.
43
+ * 2. Agent's `remembers()` — supports sync OR async returns.
44
+ *
45
+ * Mirrors {@link resolveAutoPersistSpec} so Phase 2's auto-inject
46
+ * middleware can drop in alongside the conversation-persistence flow.
47
+ */
48
+ export declare function resolveRemembersSpec(agentDecl: () => false | RemembersSpec | Promise<false | RemembersSpec>, perCall: RemembersOverride | undefined): Promise<RemembersSpec | null>;
49
+ /**
50
+ * Lookup signature used by Phase 2/3 middleware to find the registered
51
+ * {@link UserMemory} without taking a hard dep on `agent.ts`. Wired to
52
+ * `setUserMemory()` / `AiProvider`'s `ai.memory` DI binding.
53
+ */
54
+ export type UserMemoryLookup = () => UserMemory | null | undefined;
55
+ //# sourceMappingURL=memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,UAAU,EACX,MAAM,YAAY,CAAA;AAOnB;;;;;;;;;;;;;;GAcG;AACH,qBAAa,gBAAiB,YAAW,UAAU;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IAEnD,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAI,MAAM,EACd,IAAI,CAAC,EAAG;QAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC1C,OAAO,CAAC,WAAW,CAAC;IAajB,MAAM,CACV,MAAM,EAAE,MAAM,EACd,KAAK,EAAG,MAAM,EACd,IAAI,CAAC,EAAG;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAC1C,OAAO,CAAC,WAAW,EAAE,CAAC;IAcnB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrD,IAAI,CACR,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAG;QAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC1C,OAAO,CAAC,WAAW,EAAE,CAAC;IAMnB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM9C,OAAO,CAAC,UAAU;CAOnB;AA8BD;;;;;;;;;;;;GAYG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,KAAK,GAAG,aAAa,GAAG,OAAO,CAAC,KAAK,GAAG,aAAa,CAAC,EACvE,OAAO,EAAI,iBAAiB,GAAG,SAAS,GACvC,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAW/B;AAED;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,UAAU,GAAG,IAAI,GAAG,SAAS,CAAA"}
package/dist/memory.js ADDED
@@ -0,0 +1,132 @@
1
+ function generateId() {
2
+ if (typeof globalThis.crypto?.randomUUID === 'function')
3
+ return globalThis.crypto.randomUUID();
4
+ return `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
5
+ }
6
+ /**
7
+ * In-process, Map-backed {@link UserMemory}. Ships in the runtime-agnostic
8
+ * main entry — same pattern as {@link MemoryConversationStore}. Suitable
9
+ * for tests and dev; production apps configure an ORM- or
10
+ * embedding-backed store via `AiConfig.memory`.
11
+ *
12
+ * `recall()` uses case-insensitive **token-overlap** matching against
13
+ * `fact + tags`: the query is tokenized on non-alphanumeric boundaries
14
+ * and any fact sharing at least one token (≥3 chars) is returned. This
15
+ * lets natural-language queries like "what is my project?" pull facts
16
+ * containing "project" without forcing the caller to extract keywords.
17
+ * Matches return in insertion order with no scoring (binary yes/no).
18
+ * Tag filters apply before the token check — they intersect with the
19
+ * entry's own tags.
20
+ */
21
+ export class MemoryUserMemory {
22
+ entries = new Map();
23
+ async remember(userId, fact, opts) {
24
+ const entry = {
25
+ id: generateId(),
26
+ userId,
27
+ fact,
28
+ createdAt: new Date(),
29
+ ...(opts?.tags !== undefined ? { tags: opts.tags } : {}),
30
+ ...(opts?.score !== undefined ? { score: opts.score } : {}),
31
+ };
32
+ this.entries.set(entry.id, entry);
33
+ return entry;
34
+ }
35
+ async recall(userId, query, opts) {
36
+ const queryTokens = tokenize(query);
37
+ const wanted = opts?.tags;
38
+ const matches = this.allForUser(userId)
39
+ .filter(e => matchesTags(e, wanted))
40
+ .filter(e => {
41
+ if (queryTokens.size === 0)
42
+ return true;
43
+ const factTokens = tokenize(`${e.fact} ${(e.tags ?? []).join(' ')}`);
44
+ for (const t of factTokens)
45
+ if (queryTokens.has(t))
46
+ return true;
47
+ return false;
48
+ });
49
+ return capLimit(matches, opts?.limit);
50
+ }
51
+ async forget(userId, factId) {
52
+ const entry = this.entries.get(factId);
53
+ if (entry && entry.userId === userId)
54
+ this.entries.delete(factId);
55
+ }
56
+ async list(userId, opts) {
57
+ const wanted = opts?.tags;
58
+ const matches = this.allForUser(userId).filter(e => matchesTags(e, wanted));
59
+ return capLimit(matches, opts?.limit);
60
+ }
61
+ async forgetAll(userId) {
62
+ for (const [id, entry] of this.entries) {
63
+ if (entry.userId === userId)
64
+ this.entries.delete(id);
65
+ }
66
+ }
67
+ allForUser(userId) {
68
+ const out = [];
69
+ for (const entry of this.entries.values()) {
70
+ if (entry.userId === userId)
71
+ out.push(entry);
72
+ }
73
+ return out;
74
+ }
75
+ }
76
+ /**
77
+ * Lowercase + split on non-alphanumeric, drop tokens shorter than 3
78
+ * characters. Returns a Set so callers can do O(1) lookups when
79
+ * intersecting query tokens against fact tokens.
80
+ *
81
+ * Stopword filtering deliberately omitted — the 3-char floor already
82
+ * removes "is", "a", "to", "the", "we", etc. and a real stopword list
83
+ * is locale-dependent. The token-overlap recall is meant as a
84
+ * "smarter than substring" baseline, not a search engine.
85
+ */
86
+ function tokenize(s) {
87
+ const out = new Set();
88
+ for (const tok of s.toLowerCase().split(/[^a-z0-9]+/)) {
89
+ if (tok.length >= 3)
90
+ out.add(tok);
91
+ }
92
+ return out;
93
+ }
94
+ function matchesTags(entry, wanted) {
95
+ if (!wanted || wanted.length === 0)
96
+ return true;
97
+ if (!entry.tags || entry.tags.length === 0)
98
+ return false;
99
+ return wanted.every(t => entry.tags.includes(t));
100
+ }
101
+ function capLimit(items, limit) {
102
+ return limit !== undefined && limit > 0 ? items.slice(0, limit) : items;
103
+ }
104
+ /**
105
+ * Resolves the effective {@link RemembersSpec} for a single
106
+ * `prompt()` / `stream()` call. Returns `null` when memory should be
107
+ * skipped for this call.
108
+ *
109
+ * Precedence (high → low):
110
+ * 1. Per-call `options.memory` — `false` opts out, a spec replaces the
111
+ * agent's declaration.
112
+ * 2. Agent's `remembers()` — supports sync OR async returns.
113
+ *
114
+ * Mirrors {@link resolveAutoPersistSpec} so Phase 2's auto-inject
115
+ * middleware can drop in alongside the conversation-persistence flow.
116
+ */
117
+ export async function resolveRemembersSpec(agentDecl, perCall) {
118
+ if (perCall === false)
119
+ return null;
120
+ if (perCall && typeof perCall === 'object') {
121
+ if (!perCall.user)
122
+ return null;
123
+ return perCall;
124
+ }
125
+ const declared = await agentDecl();
126
+ if (declared === false || !declared)
127
+ return null;
128
+ if (!declared.user)
129
+ return null;
130
+ return declared;
131
+ }
132
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"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"}