@tanstack/ai-memory 0.0.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 (46) hide show
  1. package/dist/esm/index.d.ts +2 -0
  2. package/dist/esm/index.js +2 -0
  3. package/dist/esm/internal/store.d.ts +95 -0
  4. package/dist/esm/internal/store.js +201 -0
  5. package/dist/esm/internal/store.js.map +1 -0
  6. package/dist/esm/internal/store.test.d.ts +1 -0
  7. package/dist/esm/middleware.d.ts +73 -0
  8. package/dist/esm/middleware.js +250 -0
  9. package/dist/esm/middleware.js.map +1 -0
  10. package/dist/esm/providers/hindsight/index.d.ts +50 -0
  11. package/dist/esm/providers/hindsight/index.js +152 -0
  12. package/dist/esm/providers/hindsight/index.js.map +1 -0
  13. package/dist/esm/providers/hindsight/tools.d.ts +16 -0
  14. package/dist/esm/providers/hindsight/tools.js +119 -0
  15. package/dist/esm/providers/hindsight/tools.js.map +1 -0
  16. package/dist/esm/providers/honcho/index.d.ts +16 -0
  17. package/dist/esm/providers/honcho/index.js +150 -0
  18. package/dist/esm/providers/honcho/index.js.map +1 -0
  19. package/dist/esm/providers/in-memory/index.d.ts +19 -0
  20. package/dist/esm/providers/in-memory/index.js +46 -0
  21. package/dist/esm/providers/in-memory/index.js.map +1 -0
  22. package/dist/esm/providers/mem0/index.d.ts +14 -0
  23. package/dist/esm/providers/mem0/index.js +150 -0
  24. package/dist/esm/providers/mem0/index.js.map +1 -0
  25. package/dist/esm/providers/redis/index.d.ts +54 -0
  26. package/dist/esm/providers/redis/index.js +118 -0
  27. package/dist/esm/providers/redis/index.js.map +1 -0
  28. package/dist/esm/types.d.ts +112 -0
  29. package/package.json +100 -0
  30. package/skills/tanstack-ai-memory/SKILL.md +99 -0
  31. package/skills/tanstack-ai-memory-hindsight/SKILL.md +40 -0
  32. package/skills/tanstack-ai-memory-honcho/SKILL.md +40 -0
  33. package/skills/tanstack-ai-memory-in-memory/SKILL.md +51 -0
  34. package/skills/tanstack-ai-memory-mem0/SKILL.md +36 -0
  35. package/skills/tanstack-ai-memory-redis/SKILL.md +83 -0
  36. package/src/index.ts +20 -0
  37. package/src/internal/store.test.ts +63 -0
  38. package/src/internal/store.ts +378 -0
  39. package/src/middleware.ts +386 -0
  40. package/src/providers/hindsight/index.ts +237 -0
  41. package/src/providers/hindsight/tools.ts +139 -0
  42. package/src/providers/honcho/index.ts +238 -0
  43. package/src/providers/in-memory/index.ts +63 -0
  44. package/src/providers/mem0/index.ts +199 -0
  45. package/src/providers/redis/index.ts +179 -0
  46. package/src/types.ts +159 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","names":[],"sources":["../../src/middleware.ts"],"sourcesContent":["import { aiEventClient } from '@tanstack/ai-event-client'\nimport type {\n ChatMiddleware,\n ChatMiddlewareConfig,\n ChatMiddlewareContext,\n ModelMessage,\n StreamChunk,\n} from '@tanstack/ai'\nimport type {\n MemoryAdapter,\n MemoryFact,\n MemoryScope,\n MemoryTurn,\n RecallResult,\n SaveReceipt,\n} from './types'\n\n/**\n * CUSTOM stream-event name carrying server-side memory state to the browser.\n * The middleware injects one of these per turn (via `onChunk`); the client\n * devtools bridge (`@tanstack/ai-client`) recognizes it and re-emits `memory:*`\n * on the browser event bus. This is how server-side memory reaches the browser\n * DevTools panel — server-emitted `aiEventClient` events never cross runtimes;\n * everything the panel shows is re-derived client-side from the chat stream\n * (mirrors how generation results ride `CUSTOM` events — see `GENERATION_EVENTS`).\n */\nexport const MEMORY_STATE_EVENT = 'memory:state'\n\n/** Payload of the {@link MEMORY_STATE_EVENT} CUSTOM chunk. Captures memory state\n * as of the turn's START — the snapshot reflects every prior turn's save; this\n * turn's own save (deferred) surfaces in the next turn's snapshot. */\nexport interface MemoryStateEventValue {\n scope: MemoryScope\n adapter: string\n /** The recall query (last user text). */\n query: string\n /** Recall metrics for the operations timeline. */\n recall: {\n fragmentCount: number\n hasTools: boolean\n systemPromptChars: number\n durationMs: number\n }\n /** Live store snapshot, when the adapter supports `inspect`/`listFacts`. */\n snapshot?: {\n takenAt: string\n data: unknown\n facts: Array<MemoryFact>\n }\n}\n\n/**\n * How the middleware participates in the run:\n * - `'recall+save'` (default): recall on init (inject prompt + tools), save on finish.\n * - `'save-only'`: skip recall entirely — persist the turn but never read/inject.\n */\nexport type MemoryMiddlewareRole = 'recall+save' | 'save-only'\n\nexport interface MemoryRecallInfo {\n scope: MemoryScope\n query: string\n result: RecallResult\n}\n\nexport interface MemorySaveInfo {\n scope: MemoryScope\n turn: MemoryTurn\n receipts: Array<SaveReceipt>\n}\n\nexport interface MemoryMiddlewareOptions {\n /** The memory backend to recall from / save to. */\n adapter: MemoryAdapter\n /**\n * Scope for every adapter call. The function form is the safer default for\n * multi-tenant apps: derive scope per request from trusted, server-validated\n * chat context — never from client input.\n */\n scope:\n | MemoryScope\n | ((ctx: ChatMiddlewareContext) => MemoryScope | Promise<MemoryScope>)\n /** Participation role. Defaults to `'recall+save'`. */\n role?: MemoryMiddlewareRole\n /** Fired after `recall` completes (post-injection), for app telemetry. */\n onRecall?: (info: MemoryRecallInfo) => void | Promise<void>\n /** Fired after the deferred `save` completes, for app telemetry. */\n onSave?: (info: MemorySaveInfo) => void | Promise<void>\n}\n\n/** Per-request scratch state, keyed by context in a module-level WeakMap so the\n * same middleware instance is safe across concurrent `chat()` calls. */\ninterface MemoryRequestState {\n resolvedScope?: MemoryScope\n lastUserText: string\n /** Pending devtools transport chunk, injected once by the first `onChunk`. */\n stateChunk?: { emitted: boolean; value: MemoryStateEventValue }\n}\n\nconst stateByCtx = new WeakMap<ChatMiddlewareContext, MemoryRequestState>()\n\n/**\n * Server-side memory middleware. Recalls relevant memory into the prompt before\n * the model runs, then defers `save` of the completed turn after it finishes.\n * All extraction/ranking/rendering lives in the adapter — this middleware only\n * wires `recall`/`save` into the chat lifecycle and emits devtools events.\n */\nexport function memoryMiddleware(\n options: MemoryMiddlewareOptions,\n): ChatMiddleware {\n const role = options.role ?? 'recall+save'\n\n async function resolveScope(\n ctx: ChatMiddlewareContext,\n state: MemoryRequestState,\n ): Promise<MemoryScope> {\n if (state.resolvedScope) return state.resolvedScope\n state.resolvedScope =\n typeof options.scope === 'function'\n ? await options.scope(ctx)\n : options.scope\n return state.resolvedScope\n }\n\n return {\n name: `memory:${options.adapter.id}`,\n\n async onConfig(ctx, config) {\n if (ctx.phase !== 'init') return\n\n const state: MemoryRequestState = { lastUserText: '' }\n stateByCtx.set(ctx, state)\n\n state.lastUserText = getMessageText(findLastUserMessage(config.messages))\n if (!state.lastUserText || role === 'save-only') return\n\n const startedAt = Date.now()\n let scope: MemoryScope\n let result: RecallResult\n try {\n scope = await resolveScope(ctx, state)\n safeEmit('memory:retrieve:started', {\n scope,\n adapter: options.adapter.id,\n query: state.lastUserText,\n timestamp: startedAt,\n })\n result = await options.adapter.recall(scope, state.lastUserText)\n } catch (error) {\n safeEmit('memory:error', {\n // Only attach scope when resolve already succeeded; otherwise omit\n // (no empty-string / partial fake identity).\n ...(state.resolvedScope ? { scope: state.resolvedScope } : {}),\n adapter: options.adapter.id,\n phase: 'recall',\n error: errorInfo(error),\n timestamp: Date.now(),\n })\n return\n }\n\n const tools = result.tools ?? []\n const recallMetrics = {\n fragmentCount: result.fragments?.length ?? 0,\n hasTools: tools.length > 0,\n systemPromptChars: result.systemPrompt.length,\n durationMs: Date.now() - startedAt,\n }\n safeEmit('memory:retrieve:completed', {\n scope,\n adapter: options.adapter.id,\n ...recallMetrics,\n timestamp: Date.now(),\n })\n await options.onRecall?.({ scope, query: state.lastUserText, result })\n\n // Stage the devtools transport chunk (recall metrics + current store\n // snapshot). Injected into the stream by `onChunk` so it reaches the\n // browser panel; see MEMORY_STATE_EVENT.\n const snapshot = await gatherSnapshot(options.adapter, scope)\n state.stateChunk = {\n emitted: false,\n value: {\n scope,\n adapter: options.adapter.id,\n query: state.lastUserText,\n recall: recallMetrics,\n ...(snapshot ? { snapshot } : {}),\n },\n }\n\n const memoryPrompts = [result.toolGuidance ?? '', result.systemPrompt]\n const additions = memoryPrompts.filter((p) => p.length > 0)\n if (additions.length === 0 && tools.length === 0) return\n\n return {\n systemPrompts: [...config.systemPrompts, ...additions],\n tools: [...config.tools, ...tools],\n } satisfies Partial<ChatMiddlewareConfig>\n },\n\n onChunk(ctx, chunk) {\n // Inject the staged memory-state chunk exactly once, riding alongside the\n // first stream chunk (typically RUN_STARTED) so the browser devtools sees\n // it. Returning an array expands the stream; see ChatMiddleware.onChunk.\n const state = stateByCtx.get(ctx)\n if (!state?.stateChunk || state.stateChunk.emitted) return\n state.stateChunk.emitted = true\n const custom: StreamChunk = {\n type: 'CUSTOM',\n name: MEMORY_STATE_EVENT,\n value: state.stateChunk.value,\n timestamp: Date.now(),\n }\n return [chunk, custom]\n },\n\n onFinish(ctx, info) {\n const state = stateByCtx.get(ctx)\n stateByCtx.delete(ctx)\n const userText =\n state?.lastUserText || getMessageText(findLastUserMessage(ctx.messages))\n const assistant = info.content\n if (!userText || !assistant) return\n const scope = state?.resolvedScope\n\n ctx.defer(\n (async () => {\n // Resolve scope defensively — a throwing resolver must not escape the\n // terminal hook. Memory failures are always non-fatal + observable.\n let resolved: MemoryScope\n try {\n resolved =\n scope ?? (await resolveScope(ctx, { lastUserText: userText }))\n } catch (error) {\n safeEmit('memory:error', {\n adapter: options.adapter.id,\n phase: 'save',\n error: errorInfo(error),\n timestamp: Date.now(),\n })\n return\n }\n\n const turn: MemoryTurn = { user: userText, assistant }\n const startedAt = Date.now()\n safeEmit('memory:persist:started', {\n scope: resolved,\n adapter: options.adapter.id,\n timestamp: startedAt,\n })\n let receipts: Array<SaveReceipt>\n try {\n receipts = await options.adapter.save(resolved, turn)\n } catch (error) {\n receipts = [{ ok: false, error: String(error) }]\n safeEmit('memory:error', {\n scope: resolved,\n adapter: options.adapter.id,\n phase: 'save',\n error: errorInfo(error),\n timestamp: Date.now(),\n })\n }\n safeEmit('memory:persist:completed', {\n scope: resolved,\n adapter: options.adapter.id,\n receiptCount: receipts.length,\n okCount: receipts.filter((r) => r.ok).length,\n durationMs: Date.now() - startedAt,\n timestamp: Date.now(),\n })\n await emitSnapshot(options.adapter, resolved)\n await options.onSave?.({ scope: resolved, turn, receipts })\n })(),\n )\n },\n }\n}\n\n// ===========================\n// Internals\n// ===========================\n\n/**\n * Read the adapter's current stored state via the optional `inspect`/`listFacts`\n * introspection methods. Returns `undefined` for adapters that don't implement\n * `inspect` (they degrade to the metrics-only timeline). Fully guarded:\n * introspection must never affect chat.\n */\nasync function gatherSnapshot(\n adapter: MemoryAdapter,\n scope: MemoryScope,\n): Promise<\n { takenAt: string; data: unknown; facts: Array<MemoryFact> } | undefined\n> {\n if (!adapter.inspect) return undefined\n try {\n const snapshot = await adapter.inspect(scope)\n const facts = (await adapter.listFacts?.(scope)) ?? []\n return { takenAt: snapshot.takenAt, data: snapshot.data, facts }\n } catch {\n // ignored — introspection is best-effort telemetry.\n return undefined\n }\n}\n\n/**\n * DevTools-only: after a save, emit the adapter's current stored state on the\n * (in-process) event bus, so a devtools consumer running in the SAME runtime as\n * the chat (client-side execution / server-side listener) sees \"what's in\n * memory\". For the standard server-side topology, the browser panel instead\n * gets state via the {@link MEMORY_STATE_EVENT} stream chunk (see `onChunk`).\n */\nasync function emitSnapshot(\n adapter: MemoryAdapter,\n scope: MemoryScope,\n): Promise<void> {\n const snapshot = await gatherSnapshot(adapter, scope)\n if (!snapshot) return\n safeEmit('memory:snapshot', {\n scope,\n adapter: adapter.id,\n ...snapshot,\n timestamp: Date.now(),\n })\n}\n\nfunction findLastUserMessage(\n messages: ReadonlyArray<ModelMessage>,\n): ModelMessage | undefined {\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages[i]\n if (message && message.role === 'user') return message\n }\n return undefined\n}\n\n/**\n * Extract plain text from a `ModelMessage`. Text lives on `part.content` for\n * `TextPart`; bare strings in the content array are tolerated. All other\n * content kinds (tool-call, image, …) yield '' so they don't pollute the\n * recall query.\n */\nfunction getMessageText(message?: ModelMessage): string {\n if (!message) return ''\n if (typeof message.content === 'string') return message.content\n if (Array.isArray(message.content)) {\n return message.content\n .map((part) => {\n if (typeof part === 'string') return part\n if (part.type === 'text' && typeof part.content === 'string') {\n return part.content\n }\n return ''\n })\n .filter(Boolean)\n .join('\\n')\n }\n return ''\n}\n\nfunction errorInfo(error: unknown): { name: string; message: string } {\n if (error instanceof Error)\n return { name: error.name, message: error.message }\n if (\n error &&\n typeof error === 'object' &&\n 'name' in error &&\n typeof error.name === 'string'\n ) {\n return {\n name: error.name,\n message: String((error as { message?: unknown }).message ?? error),\n }\n }\n return { name: 'Error', message: String(error) }\n}\n\n/** Fire-and-forget devtools emit — telemetry failures must never affect chat. */\nfunction safeEmit(...args: Parameters<typeof aiEventClient.emit>): void {\n try {\n aiEventClient.emit(...args)\n } catch {\n // ignored — telemetry must not affect chat behaviour\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import { MemoryAdapter, RecallResult, SaveReceipt } from '../../types.js';
2
+ /** Recall payload shape (the subset this adapter reads). */
3
+ export interface HindsightRecallResponse {
4
+ results?: Array<{
5
+ text: string;
6
+ type?: string;
7
+ id: string;
8
+ }>;
9
+ }
10
+ /**
11
+ * Structural view of the hindsight client — only the methods this adapter uses.
12
+ * Decouples the adapter from the SDK's exact type surface.
13
+ */
14
+ export interface HindsightClientLike {
15
+ retain: (bankId: string, text: string, opts: {
16
+ context: string;
17
+ timestamp: Date;
18
+ }) => Promise<unknown>;
19
+ recall: (bankId: string, query: string, opts: {
20
+ budget: string;
21
+ }) => Promise<HindsightRecallResponse>;
22
+ reflect: (bankId: string, query: string) => Promise<{
23
+ text?: string;
24
+ }>;
25
+ listMemories: (bankId: string, opts: {
26
+ limit: number;
27
+ }) => Promise<{
28
+ items?: Array<Record<string, unknown>>;
29
+ }>;
30
+ getBankProfile: (bankId: string) => Promise<unknown>;
31
+ deleteBank: (bankId: string) => Promise<unknown>;
32
+ }
33
+ export interface HindsightRuntime {
34
+ client: HindsightClientLike;
35
+ recallToPrompt: (data: unknown) => string;
36
+ }
37
+ export interface HindsightOptions {
38
+ /** Durable user id used in the bank key. Falls back to `scope.userId`, then `'demo-user'`. */
39
+ user?: string;
40
+ /** Hindsight server URL. Defaults to `HINDSIGHT_URL` or `http://localhost:8888`. */
41
+ baseUrl?: string;
42
+ /** Recall budget. Defaults to `'mid'`. */
43
+ budget?: 'low' | 'mid' | 'high';
44
+ /** Fired when a `hindsight_retain` tool call completes. */
45
+ onToolRetain?: (receipt: SaveReceipt) => void;
46
+ /** Fired when a `hindsight_recall` tool call completes. */
47
+ onToolRecall?: (query: string, result: RecallResult) => void;
48
+ }
49
+ export declare function hindsight(options?: HindsightOptions): MemoryAdapter;
50
+ export { makeHindsightTools } from './tools.js';
@@ -0,0 +1,152 @@
1
+ import { makeHindsightTools } from "./tools.js";
2
+ //#region src/providers/hindsight/index.ts
3
+ /**
4
+ * Hindsight memory adapter. Hindsight owns extraction/ranking server-side and
5
+ * buckets memory into per-conversation "banks"
6
+ * (`{tenantId|_}__{userId}__{threadId}`). Recall
7
+ * returns a rendered prompt block AND a set of LLM tools (retain/recall/reflect)
8
+ * that let the model take direct control of memory.
9
+ *
10
+ * `@vectorize-io/hindsight-client` is an OPTIONAL peer dependency, loaded lazily.
11
+ */
12
+ var TOOL_GUIDANCE = `You have access to persistent long-term memory that survives across sessions.
13
+
14
+ Relevant memories for this turn have already been recalled and included in
15
+ your context. You also have three tools for direct control over memory:
16
+
17
+ - hindsight_retain(content): explicitly store a fact, decision, or piece of
18
+ context you want to ensure is remembered in future sessions.
19
+
20
+ - hindsight_recall(query): query memory directly with a specific question,
21
+ to look up a different topic than the user's last message.
22
+
23
+ - hindsight_reflect(question): synthesize across many memories to answer
24
+ questions that require reasoning over accumulated knowledge.
25
+
26
+ Prefer to use these tools when they would meaningfully improve your response.
27
+ You do not need to call them on every turn.`;
28
+ function hindsight(options = {}) {
29
+ const budget = options.budget ?? "mid";
30
+ let runtimePromise = null;
31
+ function getRuntime() {
32
+ if (!runtimePromise) runtimePromise = (async () => {
33
+ const mod = await import("@vectorize-io/hindsight-client");
34
+ const baseUrl = options.baseUrl ?? process.env.HINDSIGHT_URL ?? "http://localhost:8888";
35
+ return {
36
+ client: new mod.HindsightClient({ baseUrl }),
37
+ recallToPrompt: mod.recallResponseToPromptString
38
+ };
39
+ })().catch((err) => {
40
+ runtimePromise = null;
41
+ throw err;
42
+ });
43
+ return runtimePromise;
44
+ }
45
+ function bankId(scope) {
46
+ const user = options.user ?? scope.userId ?? "demo-user";
47
+ return `${scope.tenantId != null && scope.tenantId !== "" ? scope.tenantId : "_"}__${user}__${scope.threadId}`;
48
+ }
49
+ return {
50
+ id: "hindsight",
51
+ async save(scope, turn) {
52
+ const bank = bankId(scope);
53
+ const timestamp = /* @__PURE__ */ new Date();
54
+ async function retain(text, context) {
55
+ const start = Date.now();
56
+ try {
57
+ const { client } = await getRuntime();
58
+ const data = await client.retain(bank, text, {
59
+ context,
60
+ timestamp
61
+ });
62
+ return {
63
+ ok: true,
64
+ latencyMs: Date.now() - start,
65
+ raw: data
66
+ };
67
+ } catch (err) {
68
+ return {
69
+ ok: false,
70
+ latencyMs: Date.now() - start,
71
+ error: err instanceof Error ? err.message : String(err)
72
+ };
73
+ }
74
+ }
75
+ return Promise.all([retain(turn.user, "chat:user"), retain(turn.assistant, "chat:assistant")]);
76
+ },
77
+ async recall(scope, query) {
78
+ const bank = bankId(scope);
79
+ const tools = makeHindsightTools({
80
+ getRuntime,
81
+ bankId: bank,
82
+ budget,
83
+ onToolRetain: options.onToolRetain,
84
+ onToolRecall: options.onToolRecall
85
+ });
86
+ try {
87
+ const { client, recallToPrompt } = await getRuntime();
88
+ const data = await client.recall(bank, query, { budget });
89
+ const fragments = (data.results ?? []).map((r) => ({
90
+ text: r.text,
91
+ source: r.type ?? r.id
92
+ }));
93
+ return {
94
+ systemPrompt: recallToPrompt(data),
95
+ fragments,
96
+ tools,
97
+ toolGuidance: TOOL_GUIDANCE,
98
+ raw: data
99
+ };
100
+ } catch (err) {
101
+ return {
102
+ systemPrompt: "",
103
+ fragments: [],
104
+ tools,
105
+ toolGuidance: TOOL_GUIDANCE,
106
+ raw: { error: err instanceof Error ? err.message : String(err) }
107
+ };
108
+ }
109
+ },
110
+ async inspect(scope) {
111
+ const bank = bankId(scope);
112
+ try {
113
+ const { client } = await getRuntime();
114
+ const [memories, profile] = await Promise.all([client.listMemories(bank, { limit: 200 }), client.getBankProfile(bank)]);
115
+ return {
116
+ takenAt: (/* @__PURE__ */ new Date()).toISOString(),
117
+ data: {
118
+ memories,
119
+ profile
120
+ }
121
+ };
122
+ } catch (err) {
123
+ return {
124
+ takenAt: (/* @__PURE__ */ new Date()).toISOString(),
125
+ data: { error: err instanceof Error ? err.message : String(err) }
126
+ };
127
+ }
128
+ },
129
+ async listFacts(scope) {
130
+ const bank = bankId(scope);
131
+ try {
132
+ const { client } = await getRuntime();
133
+ return ((await client.listMemories(bank, { limit: 200 })).items ?? []).map((m, i) => {
134
+ const text = (typeof m.text === "string" ? m.text : void 0) ?? (typeof m.content === "string" ? m.content : void 0);
135
+ if (!text) return null;
136
+ return {
137
+ id: typeof m.id === "string" ? m.id : `hindsight-${i}`,
138
+ text,
139
+ source: typeof m.context === "string" ? m.context : "memory",
140
+ createdAt: typeof m.created_at === "string" ? m.created_at : void 0
141
+ };
142
+ }).filter((f) => f !== null);
143
+ } catch {
144
+ return [];
145
+ }
146
+ }
147
+ };
148
+ }
149
+ //#endregion
150
+ export { hindsight, makeHindsightTools };
151
+
152
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../src/providers/hindsight/index.ts"],"sourcesContent":["/**\n * Hindsight memory adapter. Hindsight owns extraction/ranking server-side and\n * buckets memory into per-conversation \"banks\"\n * (`{tenantId|_}__{userId}__{threadId}`). Recall\n * returns a rendered prompt block AND a set of LLM tools (retain/recall/reflect)\n * that let the model take direct control of memory.\n *\n * `@vectorize-io/hindsight-client` is an OPTIONAL peer dependency, loaded lazily.\n */\n\nimport { makeHindsightTools } from './tools'\nimport type {\n MemoryAdapter,\n MemoryFact,\n MemoryFragment,\n MemoryScope,\n MemorySnapshot,\n MemoryTurn,\n RecallResult,\n SaveReceipt,\n} from '../../types'\n\n/** Recall payload shape (the subset this adapter reads). */\nexport interface HindsightRecallResponse {\n results?: Array<{ text: string; type?: string; id: string }>\n}\n\n/**\n * Structural view of the hindsight client — only the methods this adapter uses.\n * Decouples the adapter from the SDK's exact type surface.\n */\nexport interface HindsightClientLike {\n retain: (\n bankId: string,\n text: string,\n opts: { context: string; timestamp: Date },\n ) => Promise<unknown>\n recall: (\n bankId: string,\n query: string,\n opts: { budget: string },\n ) => Promise<HindsightRecallResponse>\n reflect: (bankId: string, query: string) => Promise<{ text?: string }>\n listMemories: (\n bankId: string,\n opts: { limit: number },\n ) => Promise<{ items?: Array<Record<string, unknown>> }>\n getBankProfile: (bankId: string) => Promise<unknown>\n deleteBank: (bankId: string) => Promise<unknown>\n}\n\nexport interface HindsightRuntime {\n client: HindsightClientLike\n recallToPrompt: (data: unknown) => string\n}\n\nexport interface HindsightOptions {\n /** Durable user id used in the bank key. Falls back to `scope.userId`, then `'demo-user'`. */\n user?: string\n /** Hindsight server URL. Defaults to `HINDSIGHT_URL` or `http://localhost:8888`. */\n baseUrl?: string\n /** Recall budget. Defaults to `'mid'`. */\n budget?: 'low' | 'mid' | 'high'\n /** Fired when a `hindsight_retain` tool call completes. */\n onToolRetain?: (receipt: SaveReceipt) => void\n /** Fired when a `hindsight_recall` tool call completes. */\n onToolRecall?: (query: string, result: RecallResult) => void\n}\n\nconst TOOL_GUIDANCE = `You have access to persistent long-term memory that survives across sessions.\n\nRelevant memories for this turn have already been recalled and included in\nyour context. You also have three tools for direct control over memory:\n\n- hindsight_retain(content): explicitly store a fact, decision, or piece of\n context you want to ensure is remembered in future sessions.\n\n- hindsight_recall(query): query memory directly with a specific question,\n to look up a different topic than the user's last message.\n\n- hindsight_reflect(question): synthesize across many memories to answer\n questions that require reasoning over accumulated knowledge.\n\nPrefer to use these tools when they would meaningfully improve your response.\nYou do not need to call them on every turn.`\n\nexport function hindsight(options: HindsightOptions = {}): MemoryAdapter {\n const budget = options.budget ?? 'mid'\n let runtimePromise: Promise<HindsightRuntime> | null = null\n\n function getRuntime(): Promise<HindsightRuntime> {\n if (!runtimePromise) {\n runtimePromise = (async () => {\n const mod = await import('@vectorize-io/hindsight-client')\n const baseUrl =\n options.baseUrl ??\n process.env.HINDSIGHT_URL ??\n 'http://localhost:8888'\n // oxlint-disable-next-line eslint-js/no-restricted-syntax -- intentionally decoupled from the SDK's exact client type; the adapter only uses the HindsightClientLike subset\n const client = new mod.HindsightClient({\n baseUrl,\n }) as unknown as HindsightClientLike\n const recallToPrompt = mod.recallResponseToPromptString as (\n data: unknown,\n ) => string\n return { client, recallToPrompt }\n })().catch((err) => {\n runtimePromise = null\n throw err\n })\n }\n return runtimePromise\n }\n\n function bankId(scope: MemoryScope): string {\n const user = options.user ?? scope.userId ?? 'demo-user'\n // Include tenant so multi-tenant deploys cannot share banks when user+thread\n // collide. Unset tenant uses `_` (same placeholder convention as redis).\n const tenant =\n scope.tenantId != null && scope.tenantId !== '' ? scope.tenantId : '_'\n return `${tenant}__${user}__${scope.threadId}`\n }\n\n return {\n id: 'hindsight',\n\n async save(scope, turn: MemoryTurn): Promise<Array<SaveReceipt>> {\n const bank = bankId(scope)\n const timestamp = new Date()\n async function retain(\n text: string,\n context: string,\n ): Promise<SaveReceipt> {\n const start = Date.now()\n try {\n const { client } = await getRuntime()\n const data = await client.retain(bank, text, { context, timestamp })\n return { ok: true, latencyMs: Date.now() - start, raw: data }\n } catch (err) {\n return {\n ok: false,\n latencyMs: Date.now() - start,\n error: err instanceof Error ? err.message : String(err),\n }\n }\n }\n return Promise.all([\n retain(turn.user, 'chat:user'),\n retain(turn.assistant, 'chat:assistant'),\n ])\n },\n\n async recall(scope, query): Promise<RecallResult> {\n const bank = bankId(scope)\n const tools = makeHindsightTools({\n getRuntime,\n bankId: bank,\n budget,\n onToolRetain: options.onToolRetain,\n onToolRecall: options.onToolRecall,\n })\n try {\n const { client, recallToPrompt } = await getRuntime()\n const data = await client.recall(bank, query, { budget })\n const fragments: Array<MemoryFragment> = (data.results ?? []).map(\n (r) => ({\n text: r.text,\n source: r.type ?? r.id,\n }),\n )\n return {\n systemPrompt: recallToPrompt(data),\n fragments,\n tools,\n toolGuidance: TOOL_GUIDANCE,\n raw: data,\n }\n } catch (err) {\n return {\n systemPrompt: '',\n fragments: [],\n tools,\n toolGuidance: TOOL_GUIDANCE,\n raw: { error: err instanceof Error ? err.message : String(err) },\n }\n }\n },\n\n async inspect(scope): Promise<MemorySnapshot> {\n const bank = bankId(scope)\n try {\n const { client } = await getRuntime()\n const [memories, profile] = await Promise.all([\n client.listMemories(bank, { limit: 200 }),\n client.getBankProfile(bank),\n ])\n return {\n takenAt: new Date().toISOString(),\n data: { memories, profile },\n }\n } catch (err) {\n return {\n takenAt: new Date().toISOString(),\n data: { error: err instanceof Error ? err.message : String(err) },\n }\n }\n },\n\n async listFacts(scope): Promise<Array<MemoryFact>> {\n const bank = bankId(scope)\n try {\n const { client } = await getRuntime()\n const res = await client.listMemories(bank, { limit: 200 })\n const items = res.items ?? []\n return items\n .map((m, i): MemoryFact | null => {\n const text =\n (typeof m.text === 'string' ? m.text : undefined) ??\n (typeof m.content === 'string' ? m.content : undefined)\n if (!text) return null\n return {\n id: typeof m.id === 'string' ? m.id : `hindsight-${i}`,\n text,\n source: typeof m.context === 'string' ? m.context : 'memory',\n createdAt:\n typeof m.created_at === 'string' ? m.created_at : undefined,\n }\n })\n .filter((f): f is MemoryFact => f !== null)\n } catch {\n return []\n }\n },\n }\n}\n\nexport { makeHindsightTools } from './tools'\n"],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { Tool } from '@tanstack/ai';
2
+ import { RecallResult, SaveReceipt } from '../../types.js';
3
+ import { HindsightRuntime } from './index.js';
4
+ export interface HindsightToolDeps {
5
+ getRuntime: () => Promise<HindsightRuntime>;
6
+ bankId: string;
7
+ budget: string;
8
+ onToolRetain?: (receipt: SaveReceipt) => void;
9
+ onToolRecall?: (query: string, result: RecallResult) => void;
10
+ }
11
+ /**
12
+ * Build the hindsight LLM tools (retain / recall / reflect). These let the model
13
+ * take direct control of long-term memory beyond the automatic recall/save the
14
+ * middleware performs. Returned in `RecallResult.tools` and merged into the run.
15
+ */
16
+ export declare function makeHindsightTools(deps: HindsightToolDeps): Array<Tool>;
@@ -0,0 +1,119 @@
1
+ //#region src/providers/hindsight/tools.ts
2
+ function stringField(args, key) {
3
+ if (args && typeof args === "object" && key in args) {
4
+ const value = args[key];
5
+ if (typeof value === "string") return value;
6
+ }
7
+ return "";
8
+ }
9
+ /**
10
+ * Build the hindsight LLM tools (retain / recall / reflect). These let the model
11
+ * take direct control of long-term memory beyond the automatic recall/save the
12
+ * middleware performs. Returned in `RecallResult.tools` and merged into the run.
13
+ */
14
+ function makeHindsightTools(deps) {
15
+ return [
16
+ {
17
+ name: "hindsight_retain",
18
+ description: "Explicitly store a fact, decision, or piece of context to remember in future sessions. Call this when the user shares something important about themselves, their preferences, their work, or any detail that should persist beyond this conversation.",
19
+ inputSchema: {
20
+ type: "object",
21
+ properties: { content: {
22
+ type: "string",
23
+ description: "The exact fact, decision, or piece of context to store. Write it as a self-contained statement that will still make sense out of conversation context."
24
+ } },
25
+ required: ["content"],
26
+ additionalProperties: false
27
+ },
28
+ async execute(args) {
29
+ const content = stringField(args, "content");
30
+ const start = Date.now();
31
+ try {
32
+ const { client } = await deps.getRuntime();
33
+ const data = await client.retain(deps.bankId, content, {
34
+ context: "chat:tool",
35
+ timestamp: /* @__PURE__ */ new Date()
36
+ });
37
+ deps.onToolRetain?.({
38
+ ok: true,
39
+ latencyMs: Date.now() - start,
40
+ raw: data
41
+ });
42
+ return { ok: true };
43
+ } catch (err) {
44
+ const error = err instanceof Error ? err.message : String(err);
45
+ deps.onToolRetain?.({
46
+ ok: false,
47
+ latencyMs: Date.now() - start,
48
+ error
49
+ });
50
+ return {
51
+ ok: false,
52
+ error
53
+ };
54
+ }
55
+ }
56
+ },
57
+ {
58
+ name: "hindsight_recall",
59
+ description: "Query memory directly with a specific question. Use this when you need context that may not have surfaced in the automatic recall — for example, to look up a different topic than the user's last message, or to find facts about an entity mentioned in passing.",
60
+ inputSchema: {
61
+ type: "object",
62
+ properties: { query: {
63
+ type: "string",
64
+ description: "Natural-language question or topic to look up."
65
+ } },
66
+ required: ["query"],
67
+ additionalProperties: false
68
+ },
69
+ async execute(args) {
70
+ const query = stringField(args, "query");
71
+ const start = Date.now();
72
+ try {
73
+ const { client, recallToPrompt } = await deps.getRuntime();
74
+ const data = await client.recall(deps.bankId, query, { budget: deps.budget });
75
+ const systemPrompt = recallToPrompt(data);
76
+ const fragments = (data.results ?? []).map((r) => ({
77
+ text: r.text,
78
+ source: r.type ?? r.id
79
+ }));
80
+ deps.onToolRecall?.(query, {
81
+ systemPrompt,
82
+ fragments,
83
+ latencyMs: Date.now() - start,
84
+ raw: data
85
+ });
86
+ return systemPrompt || "(no relevant memories found)";
87
+ } catch (err) {
88
+ return `(no memory available: ${err instanceof Error ? err.message : String(err)})`;
89
+ }
90
+ }
91
+ },
92
+ {
93
+ name: "hindsight_reflect",
94
+ description: "Synthesize across many memories to answer questions that require reasoning over accumulated knowledge, rather than retrieving specific facts. Use this for questions like \"what do I know about this user's stack?\" or \"what has the user been working on lately?\"",
95
+ inputSchema: {
96
+ type: "object",
97
+ properties: { query: {
98
+ type: "string",
99
+ description: "The synthesis question to reflect on, e.g. \"what do I know about the user's preferences?\""
100
+ } },
101
+ required: ["query"],
102
+ additionalProperties: false
103
+ },
104
+ async execute(args) {
105
+ const query = stringField(args, "query");
106
+ try {
107
+ const { client } = await deps.getRuntime();
108
+ return (await client.reflect(deps.bankId, query)).text ?? "(no reflection)";
109
+ } catch (err) {
110
+ return `(reflection failed: ${err instanceof Error ? err.message : String(err)})`;
111
+ }
112
+ }
113
+ }
114
+ ];
115
+ }
116
+ //#endregion
117
+ export { makeHindsightTools };
118
+
119
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","names":[],"sources":["../../../../src/providers/hindsight/tools.ts"],"sourcesContent":["import type { Tool } from '@tanstack/ai'\nimport type { MemoryFragment, RecallResult, SaveReceipt } from '../../types'\nimport type { HindsightRuntime } from './index'\n\nexport interface HindsightToolDeps {\n getRuntime: () => Promise<HindsightRuntime>\n bankId: string\n budget: string\n onToolRetain?: (receipt: SaveReceipt) => void\n onToolRecall?: (query: string, result: RecallResult) => void\n}\n\nfunction stringField(args: unknown, key: string): string {\n if (args && typeof args === 'object' && key in args) {\n const value = (args as Record<string, unknown>)[key]\n if (typeof value === 'string') return value\n }\n return ''\n}\n\n/**\n * Build the hindsight LLM tools (retain / recall / reflect). These let the model\n * take direct control of long-term memory beyond the automatic recall/save the\n * middleware performs. Returned in `RecallResult.tools` and merged into the run.\n */\nexport function makeHindsightTools(deps: HindsightToolDeps): Array<Tool> {\n const retainTool: Tool = {\n name: 'hindsight_retain',\n description:\n 'Explicitly store a fact, decision, or piece of context to remember in future sessions. Call this when the user shares something important about themselves, their preferences, their work, or any detail that should persist beyond this conversation.',\n inputSchema: {\n type: 'object',\n properties: {\n content: {\n type: 'string',\n description:\n 'The exact fact, decision, or piece of context to store. Write it as a self-contained statement that will still make sense out of conversation context.',\n },\n },\n required: ['content'],\n additionalProperties: false,\n },\n async execute(args) {\n const content = stringField(args, 'content')\n const start = Date.now()\n try {\n const { client } = await deps.getRuntime()\n const data = await client.retain(deps.bankId, content, {\n context: 'chat:tool',\n timestamp: new Date(),\n })\n deps.onToolRetain?.({\n ok: true,\n latencyMs: Date.now() - start,\n raw: data,\n })\n return { ok: true }\n } catch (err) {\n const error = err instanceof Error ? err.message : String(err)\n deps.onToolRetain?.({ ok: false, latencyMs: Date.now() - start, error })\n return { ok: false, error }\n }\n },\n }\n\n const recallTool: Tool = {\n name: 'hindsight_recall',\n description:\n \"Query memory directly with a specific question. Use this when you need context that may not have surfaced in the automatic recall — for example, to look up a different topic than the user's last message, or to find facts about an entity mentioned in passing.\",\n inputSchema: {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n description: 'Natural-language question or topic to look up.',\n },\n },\n required: ['query'],\n additionalProperties: false,\n },\n async execute(args) {\n const query = stringField(args, 'query')\n const start = Date.now()\n try {\n const { client, recallToPrompt } = await deps.getRuntime()\n const data = await client.recall(deps.bankId, query, {\n budget: deps.budget,\n })\n const systemPrompt = recallToPrompt(data)\n const fragments: Array<MemoryFragment> = (data.results ?? []).map(\n (r) => ({\n text: r.text,\n source: r.type ?? r.id,\n }),\n )\n deps.onToolRecall?.(query, {\n systemPrompt,\n fragments,\n latencyMs: Date.now() - start,\n raw: data,\n } as RecallResult)\n return systemPrompt || '(no relevant memories found)'\n } catch (err) {\n const error = err instanceof Error ? err.message : String(err)\n return `(no memory available: ${error})`\n }\n },\n }\n\n const reflectTool: Tool = {\n name: 'hindsight_reflect',\n description:\n 'Synthesize across many memories to answer questions that require reasoning over accumulated knowledge, rather than retrieving specific facts. Use this for questions like \"what do I know about this user\\'s stack?\" or \"what has the user been working on lately?\"',\n inputSchema: {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n description:\n 'The synthesis question to reflect on, e.g. \"what do I know about the user\\'s preferences?\"',\n },\n },\n required: ['query'],\n additionalProperties: false,\n },\n async execute(args) {\n const query = stringField(args, 'query')\n try {\n const { client } = await deps.getRuntime()\n const data = await client.reflect(deps.bankId, query)\n return data.text ?? '(no reflection)'\n } catch (err) {\n return `(reflection failed: ${err instanceof Error ? err.message : String(err)})`\n }\n },\n }\n\n return [retainTool, recallTool, reflectTool]\n}\n"],"mappings":";AAYA,SAAS,YAAY,MAAe,KAAqB;CACvD,IAAI,QAAQ,OAAO,SAAS,YAAY,OAAO,MAAM;EACnD,MAAM,QAAS,KAAiC;EAChD,IAAI,OAAO,UAAU,UAAU,OAAO;CACxC;CACA,OAAO;AACT;;;;;;AAOA,SAAgB,mBAAmB,MAAsC;CAgHvE,OAAO;EAAC;GA9GN,MAAM;GACN,aACE;GACF,aAAa;IACX,MAAM;IACN,YAAY,EACV,SAAS;KACP,MAAM;KACN,aACE;IACJ,EACF;IACA,UAAU,CAAC,SAAS;IACpB,sBAAsB;GACxB;GACA,MAAM,QAAQ,MAAM;IAClB,MAAM,UAAU,YAAY,MAAM,SAAS;IAC3C,MAAM,QAAQ,KAAK,IAAI;IACvB,IAAI;KACF,MAAM,EAAE,WAAW,MAAM,KAAK,WAAW;KACzC,MAAM,OAAO,MAAM,OAAO,OAAO,KAAK,QAAQ,SAAS;MACrD,SAAS;MACT,2BAAW,IAAI,KAAK;KACtB,CAAC;KACD,KAAK,eAAe;MAClB,IAAI;MACJ,WAAW,KAAK,IAAI,IAAI;MACxB,KAAK;KACP,CAAC;KACD,OAAO,EAAE,IAAI,KAAK;IACpB,SAAS,KAAK;KACZ,MAAM,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;KAC7D,KAAK,eAAe;MAAE,IAAI;MAAO,WAAW,KAAK,IAAI,IAAI;MAAO;KAAM,CAAC;KACvE,OAAO;MAAE,IAAI;MAAO;KAAM;IAC5B;GACF;EA2EM;EAAY;GAvElB,MAAM;GACN,aACE;GACF,aAAa;IACX,MAAM;IACN,YAAY,EACV,OAAO;KACL,MAAM;KACN,aAAa;IACf,EACF;IACA,UAAU,CAAC,OAAO;IAClB,sBAAsB;GACxB;GACA,MAAM,QAAQ,MAAM;IAClB,MAAM,QAAQ,YAAY,MAAM,OAAO;IACvC,MAAM,QAAQ,KAAK,IAAI;IACvB,IAAI;KACF,MAAM,EAAE,QAAQ,mBAAmB,MAAM,KAAK,WAAW;KACzD,MAAM,OAAO,MAAM,OAAO,OAAO,KAAK,QAAQ,OAAO,EACnD,QAAQ,KAAK,OACf,CAAC;KACD,MAAM,eAAe,eAAe,IAAI;KACxC,MAAM,aAAoC,KAAK,WAAW,CAAC,EAAA,CAAG,KAC3D,OAAO;MACN,MAAM,EAAE;MACR,QAAQ,EAAE,QAAQ,EAAE;KACtB,EACF;KACA,KAAK,eAAe,OAAO;MACzB;MACA;MACA,WAAW,KAAK,IAAI,IAAI;MACxB,KAAK;KACP,CAAiB;KACjB,OAAO,gBAAgB;IACzB,SAAS,KAAK;KAEZ,OAAO,yBADO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EACvB;IACxC;GACF;EA+BkB;EAAY;GA3B9B,MAAM;GACN,aACE;GACF,aAAa;IACX,MAAM;IACN,YAAY,EACV,OAAO;KACL,MAAM;KACN,aACE;IACJ,EACF;IACA,UAAU,CAAC,OAAO;IAClB,sBAAsB;GACxB;GACA,MAAM,QAAQ,MAAM;IAClB,MAAM,QAAQ,YAAY,MAAM,OAAO;IACvC,IAAI;KACF,MAAM,EAAE,WAAW,MAAM,KAAK,WAAW;KAEzC,QAAO,MADY,OAAO,QAAQ,KAAK,QAAQ,KAAK,EAAA,CACxC,QAAQ;IACtB,SAAS,KAAK;KACZ,OAAO,uBAAuB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;IACjF;GACF;EAG8B;CAAW;AAC7C"}
@@ -0,0 +1,16 @@
1
+ import { MemoryAdapter, MemoryFact } from '../../types.js';
2
+ export interface HonchoOptions {
3
+ /** Durable user id. Falls back to `scope.userId`, then `'demo-user'`. */
4
+ user?: string;
5
+ /** Honcho server URL. Defaults to `HONCHO_URL` or `http://localhost:8001`. */
6
+ baseURL?: string;
7
+ /** Workspace id. Defaults to `HONCHO_APP_NAME` or `'ai-memory'`. */
8
+ workspaceId?: string;
9
+ /** API key. Defaults to `HONCHO_API_KEY` (or `'dev-no-auth'`). */
10
+ apiKey?: string;
11
+ /** Assistant peer id. Defaults to `'assistant'`. */
12
+ assistantId?: string;
13
+ }
14
+ /** Parse a Honcho `peer.representation()` text blob into flat fact rows. */
15
+ export declare function parseHonchoRepresentation(raw: string): Array<MemoryFact>;
16
+ export declare function honcho(options?: HonchoOptions): MemoryAdapter;
@@ -0,0 +1,150 @@
1
+ //#region src/providers/honcho/index.ts
2
+ async function timed(fn) {
3
+ const start = Date.now();
4
+ try {
5
+ const data = await fn();
6
+ return {
7
+ ok: true,
8
+ latencyMs: Date.now() - start,
9
+ data
10
+ };
11
+ } catch (err) {
12
+ return {
13
+ ok: false,
14
+ latencyMs: Date.now() - start,
15
+ error: err instanceof Error ? err.message : String(err)
16
+ };
17
+ }
18
+ }
19
+ var HONCHO_LINE_RE = /^\[(?<ts>[^\]]+)\]\s+(?<text>.+)$/;
20
+ /** Parse a Honcho `peer.representation()` text blob into flat fact rows. */
21
+ function parseHonchoRepresentation(raw) {
22
+ return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("##") && !line.startsWith("Explicit Observations")).map((line, i) => {
23
+ const m = line.match(HONCHO_LINE_RE);
24
+ if (m?.groups?.ts && m.groups.text) return {
25
+ id: `honcho-${m.groups.ts}-${i}`,
26
+ text: m.groups.text,
27
+ source: "observation",
28
+ createdAt: m.groups.ts
29
+ };
30
+ return {
31
+ id: `honcho-${i}`,
32
+ text: line,
33
+ source: "representation"
34
+ };
35
+ });
36
+ }
37
+ function honcho(options = {}) {
38
+ const assistantId = options.assistantId ?? "assistant";
39
+ let clientPromise = null;
40
+ const sessionCache = /* @__PURE__ */ new Map();
41
+ const userPeerCache = /* @__PURE__ */ new Map();
42
+ let assistantPeerPromise = null;
43
+ async function loadClient() {
44
+ return new (await (import("@honcho-ai/sdk"))).Honcho({
45
+ baseURL: options.baseURL ?? process.env.HONCHO_URL ?? "http://localhost:8001",
46
+ workspaceId: options.workspaceId ?? process.env.HONCHO_APP_NAME ?? "ai-memory",
47
+ apiKey: options.apiKey ?? process.env.HONCHO_API_KEY ?? "dev-no-auth"
48
+ });
49
+ }
50
+ function getClient() {
51
+ if (!clientPromise) clientPromise = loadClient();
52
+ return clientPromise;
53
+ }
54
+ function cached(cache, key, create) {
55
+ const existing = cache.get(key);
56
+ if (existing) return existing;
57
+ const created = create().catch((err) => {
58
+ if (cache.get(key) === created) cache.delete(key);
59
+ throw err;
60
+ });
61
+ cache.set(key, created);
62
+ return created;
63
+ }
64
+ function getUserPeer(userId) {
65
+ return cached(userPeerCache, userId, async () => (await getClient()).peer(userId));
66
+ }
67
+ function getAssistantPeer() {
68
+ if (!assistantPeerPromise) assistantPeerPromise = (async () => (await getClient()).peer(assistantId))().catch((err) => {
69
+ assistantPeerPromise = null;
70
+ throw err;
71
+ });
72
+ return assistantPeerPromise;
73
+ }
74
+ function getSession(sessionKey) {
75
+ return cached(sessionCache, sessionKey, async () => (await getClient()).session(sessionKey));
76
+ }
77
+ /** Honcho session id — tenant-qualified so tenants cannot share sessions. */
78
+ function sessionKeyFor(scope) {
79
+ return `${scope.tenantId != null && scope.tenantId !== "" ? scope.tenantId : "_"}__${scope.threadId}`;
80
+ }
81
+ /**
82
+ * Honcho peer id. When `tenantId` is set, prefix the durable user so peers
83
+ * cannot collide across tenants.
84
+ */
85
+ function userIdFor(scope) {
86
+ const user = options.user ?? scope.userId ?? "demo-user";
87
+ if (scope.tenantId != null && scope.tenantId !== "") return `${scope.tenantId}__${user}`;
88
+ return user;
89
+ }
90
+ return {
91
+ id: "honcho",
92
+ async save(scope, turn) {
93
+ const result = await timed(async () => {
94
+ const [userPeer, assistantPeer, session] = await Promise.all([
95
+ getUserPeer(userIdFor(scope)),
96
+ getAssistantPeer(),
97
+ getSession(sessionKeyFor(scope))
98
+ ]);
99
+ return session.addMessages([userPeer.message(turn.user), assistantPeer.message(turn.assistant)]);
100
+ });
101
+ return [{
102
+ ok: result.ok,
103
+ latencyMs: result.latencyMs,
104
+ raw: result.ok ? result.data : void 0,
105
+ error: result.ok ? void 0 : result.error
106
+ }];
107
+ },
108
+ async recall(scope, query) {
109
+ const result = await timed(async () => {
110
+ const [userPeer, session] = await Promise.all([getUserPeer(userIdFor(scope)), getSession(sessionKeyFor(scope))]);
111
+ return userPeer.chat(query, { session });
112
+ });
113
+ if (!result.ok) return {
114
+ systemPrompt: "",
115
+ raw: { error: result.error }
116
+ };
117
+ const text = typeof result.data === "string" ? result.data : "";
118
+ return {
119
+ systemPrompt: text,
120
+ raw: { dialectic: text }
121
+ };
122
+ },
123
+ async inspect(scope) {
124
+ const session = await getSession(sessionKeyFor(scope)).catch(() => null);
125
+ if (!session) return {
126
+ takenAt: (/* @__PURE__ */ new Date()).toISOString(),
127
+ data: { error: "failed to get session" }
128
+ };
129
+ const [messages, summaries] = await Promise.all([timed(() => session.messages({ size: 50 })), timed(() => session.summaries())]);
130
+ return {
131
+ takenAt: (/* @__PURE__ */ new Date()).toISOString(),
132
+ data: {
133
+ messages: messages.ok ? messages.data : { error: messages.error },
134
+ summaries: summaries.ok ? summaries.data : { error: summaries.error }
135
+ }
136
+ };
137
+ },
138
+ async listFacts(scope) {
139
+ const result = await timed(async () => {
140
+ return (await getUserPeer(userIdFor(scope))).representation();
141
+ });
142
+ if (!result.ok) return [];
143
+ return parseHonchoRepresentation(typeof result.data === "string" ? result.data : String(result.data.representation ?? ""));
144
+ }
145
+ };
146
+ }
147
+ //#endregion
148
+ export { honcho, parseHonchoRepresentation };
149
+
150
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../src/providers/honcho/index.ts"],"sourcesContent":["/**\n * Honcho memory adapter. Honcho models memory as peers exchanging messages in a\n * session and answers recall via a \"dialectic\" query over the user peer's\n * representation — so `recall` returns a synthesized answer (no discrete\n * fragments) and `save` appends the turn's messages to the session.\n *\n * `@honcho-ai/sdk` is an OPTIONAL peer dependency, loaded lazily on first use.\n */\n\nimport type { Peer, Session } from '@honcho-ai/sdk'\nimport type {\n MemoryAdapter,\n MemoryFact,\n MemoryScope,\n MemorySnapshot,\n MemoryTurn,\n RecallResult,\n SaveReceipt,\n} from '../../types'\n\nexport interface HonchoOptions {\n /** Durable user id. Falls back to `scope.userId`, then `'demo-user'`. */\n user?: string\n /** Honcho server URL. Defaults to `HONCHO_URL` or `http://localhost:8001`. */\n baseURL?: string\n /** Workspace id. Defaults to `HONCHO_APP_NAME` or `'ai-memory'`. */\n workspaceId?: string\n /** API key. Defaults to `HONCHO_API_KEY` (or `'dev-no-auth'`). */\n apiKey?: string\n /** Assistant peer id. Defaults to `'assistant'`. */\n assistantId?: string\n}\n\ntype Timed<T> =\n | { ok: true; latencyMs: number; data: T }\n | { ok: false; latencyMs: number; error: string }\n\nasync function timed<T>(fn: () => Promise<T>): Promise<Timed<T>> {\n const start = Date.now()\n try {\n const data = await fn()\n return { ok: true, latencyMs: Date.now() - start, data }\n } catch (err) {\n return {\n ok: false,\n latencyMs: Date.now() - start,\n error: err instanceof Error ? err.message : String(err),\n }\n }\n}\n\nconst HONCHO_LINE_RE = /^\\[(?<ts>[^\\]]+)\\]\\s+(?<text>.+)$/\n\n/** Parse a Honcho `peer.representation()` text blob into flat fact rows. */\nexport function parseHonchoRepresentation(raw: string): Array<MemoryFact> {\n return raw\n .split('\\n')\n .map((line) => line.trim())\n .filter(\n (line) =>\n line.length > 0 &&\n !line.startsWith('##') &&\n !line.startsWith('Explicit Observations'),\n )\n .map((line, i): MemoryFact => {\n const m = line.match(HONCHO_LINE_RE)\n if (m?.groups?.ts && m.groups.text) {\n return {\n id: `honcho-${m.groups.ts}-${i}`,\n text: m.groups.text,\n source: 'observation',\n createdAt: m.groups.ts,\n }\n }\n return { id: `honcho-${i}`, text: line, source: 'representation' }\n })\n}\n\nexport function honcho(options: HonchoOptions = {}): MemoryAdapter {\n const assistantId = options.assistantId ?? 'assistant'\n\n // Client + entity caches live in this factory's closure — each honcho()\n // instance owns its own.\n type Client = Awaited<ReturnType<typeof loadClient>>\n let clientPromise: Promise<Client> | null = null\n const sessionCache = new Map<string, Promise<Session>>()\n const userPeerCache = new Map<string, Promise<Peer>>()\n let assistantPeerPromise: Promise<Peer> | null = null\n\n async function loadClient() {\n const mod = await import('@honcho-ai/sdk')\n return new mod.Honcho({\n baseURL:\n options.baseURL ?? process.env.HONCHO_URL ?? 'http://localhost:8001',\n workspaceId:\n options.workspaceId ?? process.env.HONCHO_APP_NAME ?? 'ai-memory',\n apiKey: options.apiKey ?? process.env.HONCHO_API_KEY ?? 'dev-no-auth',\n })\n }\n\n function getClient(): Promise<Client> {\n if (!clientPromise) clientPromise = loadClient()\n return clientPromise\n }\n\n function cached<T>(\n cache: Map<string, Promise<T>>,\n key: string,\n create: () => Promise<T>,\n ): Promise<T> {\n const existing = cache.get(key)\n if (existing) return existing\n const created = create().catch((err) => {\n if (cache.get(key) === created) cache.delete(key)\n throw err\n })\n cache.set(key, created)\n return created\n }\n\n function getUserPeer(userId: string): Promise<Peer> {\n return cached(userPeerCache, userId, async () =>\n (await getClient()).peer(userId),\n )\n }\n function getAssistantPeer(): Promise<Peer> {\n if (!assistantPeerPromise) {\n assistantPeerPromise = (async () =>\n (await getClient()).peer(assistantId))().catch((err) => {\n assistantPeerPromise = null\n throw err\n })\n }\n return assistantPeerPromise\n }\n function getSession(sessionKey: string): Promise<Session> {\n return cached(sessionCache, sessionKey, async () =>\n (await getClient()).session(sessionKey),\n )\n }\n\n /** Honcho session id — tenant-qualified so tenants cannot share sessions. */\n function sessionKeyFor(scope: MemoryScope): string {\n const tenant =\n scope.tenantId != null && scope.tenantId !== '' ? scope.tenantId : '_'\n return `${tenant}__${scope.threadId}`\n }\n\n /**\n * Honcho peer id. When `tenantId` is set, prefix the durable user so peers\n * cannot collide across tenants.\n */\n function userIdFor(scope: MemoryScope): string {\n const user = options.user ?? scope.userId ?? 'demo-user'\n if (scope.tenantId != null && scope.tenantId !== '') {\n return `${scope.tenantId}__${user}`\n }\n return user\n }\n\n return {\n id: 'honcho',\n\n async save(scope, turn: MemoryTurn): Promise<Array<SaveReceipt>> {\n const result = await timed(async () => {\n const [userPeer, assistantPeer, session] = await Promise.all([\n getUserPeer(userIdFor(scope)),\n getAssistantPeer(),\n getSession(sessionKeyFor(scope)),\n ])\n return session.addMessages([\n userPeer.message(turn.user),\n assistantPeer.message(turn.assistant),\n ])\n })\n return [\n {\n ok: result.ok,\n latencyMs: result.latencyMs,\n raw: result.ok ? result.data : undefined,\n error: result.ok ? undefined : result.error,\n },\n ]\n },\n\n async recall(scope, query): Promise<RecallResult> {\n const result = await timed(async () => {\n const [userPeer, session] = await Promise.all([\n getUserPeer(userIdFor(scope)),\n getSession(sessionKeyFor(scope)),\n ])\n return userPeer.chat(query, { session })\n })\n if (!result.ok) {\n return { systemPrompt: '', raw: { error: result.error } }\n }\n const text = typeof result.data === 'string' ? result.data : ''\n return { systemPrompt: text, raw: { dialectic: text } }\n },\n\n async inspect(scope): Promise<MemorySnapshot> {\n const session = await getSession(sessionKeyFor(scope)).catch(() => null)\n if (!session) {\n return {\n takenAt: new Date().toISOString(),\n data: { error: 'failed to get session' },\n }\n }\n const [messages, summaries] = await Promise.all([\n timed(() => session.messages({ size: 50 })),\n timed(() => session.summaries()),\n ])\n return {\n takenAt: new Date().toISOString(),\n data: {\n messages: messages.ok ? messages.data : { error: messages.error },\n summaries: summaries.ok ? summaries.data : { error: summaries.error },\n },\n }\n },\n\n async listFacts(scope): Promise<Array<MemoryFact>> {\n const result = await timed(async () => {\n const userPeer = await getUserPeer(userIdFor(scope))\n return userPeer.representation()\n })\n if (!result.ok) return []\n const raw =\n typeof result.data === 'string'\n ? result.data\n : String(\n (result.data as { representation?: unknown }).representation ??\n '',\n )\n return parseHonchoRepresentation(raw)\n },\n }\n}\n"],"mappings":";AAqCA,eAAe,MAAS,IAAyC;CAC/D,MAAM,QAAQ,KAAK,IAAI;CACvB,IAAI;EACF,MAAM,OAAO,MAAM,GAAG;EACtB,OAAO;GAAE,IAAI;GAAM,WAAW,KAAK,IAAI,IAAI;GAAO;EAAK;CACzD,SAAS,KAAK;EACZ,OAAO;GACL,IAAI;GACJ,WAAW,KAAK,IAAI,IAAI;GACxB,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;EACxD;CACF;AACF;AAEA,IAAM,iBAAiB;;AAGvB,SAAgB,0BAA0B,KAAgC;CACxE,OAAO,IACJ,MAAM,IAAI,CAAC,CACX,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC,CAC1B,QACE,SACC,KAAK,SAAS,KACd,CAAC,KAAK,WAAW,IAAI,KACrB,CAAC,KAAK,WAAW,uBAAuB,CAC5C,CAAC,CACA,KAAK,MAAM,MAAkB;EAC5B,MAAM,IAAI,KAAK,MAAM,cAAc;EACnC,IAAI,GAAG,QAAQ,MAAM,EAAE,OAAO,MAC5B,OAAO;GACL,IAAI,UAAU,EAAE,OAAO,GAAG,GAAG;GAC7B,MAAM,EAAE,OAAO;GACf,QAAQ;GACR,WAAW,EAAE,OAAO;EACtB;EAEF,OAAO;GAAE,IAAI,UAAU;GAAK,MAAM;GAAM,QAAQ;EAAiB;CACnE,CAAC;AACL;AAEA,SAAgB,OAAO,UAAyB,CAAC,GAAkB;CACjE,MAAM,cAAc,QAAQ,eAAe;CAK3C,IAAI,gBAAwC;CAC5C,MAAM,+BAAe,IAAI,IAA8B;CACvD,MAAM,gCAAgB,IAAI,IAA2B;CACrD,IAAI,uBAA6C;CAEjD,eAAe,aAAa;EAE1B,OAAO,KAAI,OADO,OAAO,mBAAA,CACV,OAAO;GACpB,SACE,QAAQ,WAAW,QAAQ,IAAI,cAAc;GAC/C,aACE,QAAQ,eAAe,QAAQ,IAAI,mBAAmB;GACxD,QAAQ,QAAQ,UAAU,QAAQ,IAAI,kBAAkB;EAC1D,CAAC;CACH;CAEA,SAAS,YAA6B;EACpC,IAAI,CAAC,eAAe,gBAAgB,WAAW;EAC/C,OAAO;CACT;CAEA,SAAS,OACP,OACA,KACA,QACY;EACZ,MAAM,WAAW,MAAM,IAAI,GAAG;EAC9B,IAAI,UAAU,OAAO;EACrB,MAAM,UAAU,OAAO,CAAC,CAAC,OAAO,QAAQ;GACtC,IAAI,MAAM,IAAI,GAAG,MAAM,SAAS,MAAM,OAAO,GAAG;GAChD,MAAM;EACR,CAAC;EACD,MAAM,IAAI,KAAK,OAAO;EACtB,OAAO;CACT;CAEA,SAAS,YAAY,QAA+B;EAClD,OAAO,OAAO,eAAe,QAAQ,aAClC,MAAM,UAAU,EAAA,CAAG,KAAK,MAAM,CACjC;CACF;CACA,SAAS,mBAAkC;EACzC,IAAI,CAAC,sBACH,wBAAwB,aACrB,MAAM,UAAU,EAAA,CAAG,KAAK,WAAW,EAAA,CAAG,CAAC,CAAC,OAAO,QAAQ;GACxD,uBAAuB;GACvB,MAAM;EACR,CAAC;EAEH,OAAO;CACT;CACA,SAAS,WAAW,YAAsC;EACxD,OAAO,OAAO,cAAc,YAAY,aACrC,MAAM,UAAU,EAAA,CAAG,QAAQ,UAAU,CACxC;CACF;;CAGA,SAAS,cAAc,OAA4B;EAGjD,OAAO,GADL,MAAM,YAAY,QAAQ,MAAM,aAAa,KAAK,MAAM,WAAW,IACpD,IAAI,MAAM;CAC7B;;;;;CAMA,SAAS,UAAU,OAA4B;EAC7C,MAAM,OAAO,QAAQ,QAAQ,MAAM,UAAU;EAC7C,IAAI,MAAM,YAAY,QAAQ,MAAM,aAAa,IAC/C,OAAO,GAAG,MAAM,SAAS,IAAI;EAE/B,OAAO;CACT;CAEA,OAAO;EACL,IAAI;EAEJ,MAAM,KAAK,OAAO,MAA+C;GAC/D,MAAM,SAAS,MAAM,MAAM,YAAY;IACrC,MAAM,CAAC,UAAU,eAAe,WAAW,MAAM,QAAQ,IAAI;KAC3D,YAAY,UAAU,KAAK,CAAC;KAC5B,iBAAiB;KACjB,WAAW,cAAc,KAAK,CAAC;IACjC,CAAC;IACD,OAAO,QAAQ,YAAY,CACzB,SAAS,QAAQ,KAAK,IAAI,GAC1B,cAAc,QAAQ,KAAK,SAAS,CACtC,CAAC;GACH,CAAC;GACD,OAAO,CACL;IACE,IAAI,OAAO;IACX,WAAW,OAAO;IAClB,KAAK,OAAO,KAAK,OAAO,OAAO,KAAA;IAC/B,OAAO,OAAO,KAAK,KAAA,IAAY,OAAO;GACxC,CACF;EACF;EAEA,MAAM,OAAO,OAAO,OAA8B;GAChD,MAAM,SAAS,MAAM,MAAM,YAAY;IACrC,MAAM,CAAC,UAAU,WAAW,MAAM,QAAQ,IAAI,CAC5C,YAAY,UAAU,KAAK,CAAC,GAC5B,WAAW,cAAc,KAAK,CAAC,CACjC,CAAC;IACD,OAAO,SAAS,KAAK,OAAO,EAAE,QAAQ,CAAC;GACzC,CAAC;GACD,IAAI,CAAC,OAAO,IACV,OAAO;IAAE,cAAc;IAAI,KAAK,EAAE,OAAO,OAAO,MAAM;GAAE;GAE1D,MAAM,OAAO,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO;GAC7D,OAAO;IAAE,cAAc;IAAM,KAAK,EAAE,WAAW,KAAK;GAAE;EACxD;EAEA,MAAM,QAAQ,OAAgC;GAC5C,MAAM,UAAU,MAAM,WAAW,cAAc,KAAK,CAAC,CAAC,CAAC,YAAY,IAAI;GACvE,IAAI,CAAC,SACH,OAAO;IACL,0BAAS,IAAI,KAAK,EAAA,CAAE,YAAY;IAChC,MAAM,EAAE,OAAO,wBAAwB;GACzC;GAEF,MAAM,CAAC,UAAU,aAAa,MAAM,QAAQ,IAAI,CAC9C,YAAY,QAAQ,SAAS,EAAE,MAAM,GAAG,CAAC,CAAC,GAC1C,YAAY,QAAQ,UAAU,CAAC,CACjC,CAAC;GACD,OAAO;IACL,0BAAS,IAAI,KAAK,EAAA,CAAE,YAAY;IAChC,MAAM;KACJ,UAAU,SAAS,KAAK,SAAS,OAAO,EAAE,OAAO,SAAS,MAAM;KAChE,WAAW,UAAU,KAAK,UAAU,OAAO,EAAE,OAAO,UAAU,MAAM;IACtE;GACF;EACF;EAEA,MAAM,UAAU,OAAmC;GACjD,MAAM,SAAS,MAAM,MAAM,YAAY;IAErC,QAAO,MADgB,YAAY,UAAU,KAAK,CAAC,EAAA,CACnC,eAAe;GACjC,CAAC;GACD,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC;GAQxB,OAAO,0BANL,OAAO,OAAO,SAAS,WACnB,OAAO,OACP,OACG,OAAO,KAAsC,kBAC5C,EACJ,CAC8B;EACtC;CACF;AACF"}
@@ -0,0 +1,19 @@
1
+ import { BuiltinOptions } from '../../internal/store.js';
2
+ import { MemoryAdapter } from '../../types.js';
3
+ /**
4
+ * Options for {@link inMemory}. Retrieval/extraction knobs that used to live on
5
+ * the middleware are adapter options here.
6
+ */
7
+ export interface InMemoryOptions extends BuiltinOptions {
8
+ }
9
+ /**
10
+ * Zero-dependency memory adapter backed by a `Map`. Records vanish on process
11
+ * restart, so this is for local development, tests, and single-process demos —
12
+ * not multi-process production (each worker gets its own Map). For production,
13
+ * use {@link redis} from `@tanstack/ai-memory/redis`.
14
+ *
15
+ * By default `save` stores the raw user/assistant turn and `recall` scores it
16
+ * lexically + by recency. Pass an `embedder` for semantic scoring and/or an
17
+ * `extract` function to persist derived facts.
18
+ */
19
+ export declare function inMemory(options?: InMemoryOptions): MemoryAdapter;