{"version":3,"sources":["../../../../src/server/stores/memory-hosted/index.ts","../../../../src/server/stores/memory-hosted/client.ts","../../../../src/server/http.ts"],"sourcesContent":["/**\n * Hosted memory adapter — public entry.\n *\n *   import { createHostedMemory } from '@mordn/chat-widget/server/memory/hosted';\n *   createChatHandler({\n *     memory: { adapter: createHostedMemory({ apiKey: process.env.MORDN_CHAT_KEY, agentId }) },\n *   });\n *\n * No DATABASE_URL, no pgvector, no extraction model — the hosted @mordn/chat-api\n * service owns extraction + storage. The only secret you hold is the tenant key.\n */\nimport 'server-only';\n\nexport { createHostedMemory, type HostedMemoryOptions } from './client';\n","/**\n * Hosted memory adapter — a thin HTTP client over @mordn/chat-api's\n * `/v1/memory/*` endpoints. Same `MemoryAdapter` interface as the Drizzle/mem0\n * adapters, so switching is a one-line change:\n *\n *   memory: { adapter: createHostedMemory({ apiKey: process.env.MORDN_CHAT_KEY, agentId }) }\n *\n * Identity: the `apiKey` authenticates the TENANT; the per-request `userId` the\n * handler binds is sent as `X-Chat-User` (the end user, from the consumer's\n * verified session — same trust model as getUserId). The hosted service enforces\n * tenant + agent + user scoping; this client only carries identity.\n *\n * Endpoints (per the build contract):\n *   retrieve → POST   /v1/memory/query   { agentId, query, limit }\n *   record   → POST   /v1/memory/record  { agentId, conversationId, messages }\n *   list     → GET    /v1/memory?agentId=\n *   forget   → DELETE /v1/memory/:id\n *   forgetAll→ DELETE /v1/memory?agentId=\n */\n\nimport 'server-only';\nimport type {\n  Memory,\n  MemoryAdapter,\n  MemoryAdapterFactory,\n  RecordOptions,\n  RetrieveOptions,\n} from '../../memory/types';\nimport { withFetchTimeout, DEFAULT_HTTP_TIMEOUT_MS } from '../../http';\n\nconst DEFAULT_BASE_URL = 'https://api.mordn.com';\n\nfunction normalise(raw: Record<string, unknown>): Memory {\n  return {\n    id: String(raw.id ?? ''),\n    text: String(raw.text ?? ''),\n    score: raw.score != null ? Number(raw.score) : undefined,\n    createdAt: String(raw.createdAt ?? raw.created_at ?? new Date().toISOString()),\n    metadata: (raw.metadata as Record<string, unknown>) ?? {},\n  };\n}\n\nclass HostedMemoryAdapter implements MemoryAdapter {\n  private readonly base: string;\n  private readonly doFetch: typeof fetch;\n\n  constructor(\n    public readonly userId: string,\n    private readonly apiKey: string,\n    baseUrl: string,\n    fetchImpl: typeof fetch,\n    private readonly agentId?: string,\n  ) {\n    this.base = baseUrl.replace(/\\/$/, '');\n    this.doFetch = fetchImpl;\n  }\n\n  private headers(json = false): Record<string, string> {\n    const h: Record<string, string> = {\n      Authorization: `Bearer ${this.apiKey}`,\n      'X-Chat-User': this.userId,\n      Accept: 'application/json',\n    };\n    if (json) h['Content-Type'] = 'application/json';\n    return h;\n  }\n\n  async retrieve(opts: RetrieveOptions): Promise<Memory[]> {\n    try {\n      const res = await this.doFetch(`${this.base}/v1/memory/query`, {\n        method: 'POST',\n        headers: this.headers(true),\n        body: JSON.stringify({\n          ...(this.agentId ? { agentId: this.agentId } : {}),\n          query: opts.query,\n          limit: opts.limit,\n        }),\n      });\n      if (!res.ok) return [];\n      const data = (await res.json().catch(() => null)) as { memories?: Record<string, unknown>[] } | null;\n      const rows = (data?.memories ?? []).map(normalise);\n      return opts.minScore != null ? rows.filter((m) => m.score == null || m.score >= opts.minScore!) : rows;\n    } catch {\n      return [];\n    }\n  }\n\n  async record(opts: RecordOptions): Promise<void> {\n    await this.doFetch(`${this.base}/v1/memory/record`, {\n      method: 'POST',\n      headers: this.headers(true),\n      body: JSON.stringify({\n        ...(this.agentId ? { agentId: this.agentId } : {}),\n        conversationId: opts.conversationId,\n        messages: opts.messages,\n      }),\n    }).catch(() => {});\n  }\n\n  async list(): Promise<Memory[]> {\n    const params = new URLSearchParams();\n    if (this.agentId) params.set('agentId', this.agentId);\n    const res = await this.doFetch(`${this.base}/v1/memory?${params}`, { headers: this.headers() });\n    if (!res.ok) return [];\n    const data = (await res.json().catch(() => null)) as { memories?: Record<string, unknown>[] } | null;\n    return (data?.memories ?? []).map(normalise);\n  }\n\n  async forget(id: string): Promise<void> {\n    await this.doFetch(`${this.base}/v1/memory/${encodeURIComponent(id)}`, {\n      method: 'DELETE',\n      headers: this.headers(),\n    }).catch(() => {});\n  }\n\n  async forgetAll(): Promise<void> {\n    const params = new URLSearchParams();\n    if (this.agentId) params.set('agentId', this.agentId);\n    await this.doFetch(`${this.base}/v1/memory?${params}`, {\n      method: 'DELETE',\n      headers: this.headers(),\n    }).catch(() => {});\n  }\n}\n\nexport interface HostedMemoryOptions {\n  /** Tenant API key (mck_live_… / mck_test_…). Required. Never sent to the client. */\n  apiKey: string;\n  /**\n   * Optional agent assertion for advanced multi-agent control planes. The\n   * standard hosted path omits it because the API key already selects the agent.\n   */\n  agentId?: string;\n  /** API base URL. Defaults to the hosted service; override for self-host/local. */\n  baseUrl?: string;\n  /** Optional fetch override (testing). */\n  fetch?: typeof fetch;\n  /** Per-request timeout (ms) for the hosted API. Defaults to 30s; `0` disables. */\n  timeoutMs?: number;\n}\n\n/**\n * Create a `MemoryAdapterFactory` backed by the hosted @mordn/chat-api service.\n * Pass to `createChatHandler({ memory: { adapter: createHostedMemory({ apiKey, agentId }) } })`.\n */\nexport function createHostedMemory(options: HostedMemoryOptions): MemoryAdapterFactory {\n  if (!options.apiKey) throw new Error('[chat-widget] createHostedMemory requires an apiKey');\n  const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n  const fetchImpl = withFetchTimeout(options.fetch ?? fetch, options.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS);\n  return (userId) => new HostedMemoryAdapter(userId, options.apiKey, baseUrl, fetchImpl, options.agentId);\n}\n","/**\n * Shared HTTP helper for the hosted API clients.\n *\n * Every hosted client (chat store, storage, config, feedback, knowledge, memory,\n * mem0) issues `fetch` to a remote service. Without a timeout, a hung or stalled\n * upstream holds the request — and the connection-pool slot / serverless compute\n * behind it — open until the platform kills it, which on a chat turn means a\n * wedged reply. `withFetchTimeout` bounds every call with a wall-clock deadline.\n *\n * It also defaults every request to `cache: 'no-store'`. These clients run\n * inside Next.js route handlers, whose patched `fetch` caches GETs by default —\n * keyed on URL ONLY, so a per-user header like `X-Chat-User` does not vary the\n * cache key. Left uncorrected, that means one user's hosted-backend response\n * (their conversation list, their config) can be served straight to another\n * user, and a since-updated history can be served stale. Per-user data must\n * never enter that cache, so the wrapper opts every wrapped call out of it;\n * callers that legitimately want caching can still override via `init`.\n */\n\nimport 'server-only';\n\n/** Default per-request timeout (ms) for the hosted HTTP clients. */\nexport const DEFAULT_HTTP_TIMEOUT_MS = 30_000;\n\n/**\n * Wrap a `fetch` implementation so every call is bounded by a wall-clock\n * timeout. Returns a `fetch`-compatible function: when the deadline passes the\n * underlying request is aborted (the promise rejects with an `AbortError`), so a\n * hung/stalled upstream can never hold a request open indefinitely. A\n * caller-supplied `init.signal` is still honoured (aborting it aborts the\n * request). `timeoutMs <= 0` disables the timeout and returns the impl unchanged.\n *\n * Portable across Node and Edge/Workers — uses only `AbortController` /\n * `setTimeout` / `fetch`, no runtime-specific APIs.\n */\nexport function withFetchTimeout(fetchImpl: typeof fetch, timeoutMs: number): typeof fetch {\n  if (!timeoutMs || timeoutMs <= 0) return fetchImpl;\n  const wrapped = (\n    input: Parameters<typeof fetch>[0],\n    init?: Parameters<typeof fetch>[1],\n  ): Promise<Response> => {\n    const controller = new AbortController();\n    const callerSignal = init?.signal;\n    if (callerSignal) {\n      if (callerSignal.aborted) controller.abort();\n      else callerSignal.addEventListener('abort', () => controller.abort(), { once: true });\n    }\n    const timer = setTimeout(() => controller.abort(), timeoutMs);\n    return Promise.resolve(\n      fetchImpl(input, { cache: 'no-store', ...init, signal: controller.signal }),\n    ).finally(() => clearTimeout(timer));\n  };\n  return wrapped as typeof fetch;\n}\n"],"mappings":"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","names":[]}