{"version":3,"sources":["../../../../src/server/stores/knowledge-hosted/index.ts","../../../../src/server/stores/knowledge-hosted/client.ts","../../../../src/server/http.ts"],"sourcesContent":["/**\n * Hosted Knowledge retriever — public entry.\n *\n *   import { createHostedKnowledgeRetriever } from '@mordn/chat-widget/server/knowledge/hosted';\n *   createChatHandler({\n *     retrieval: {\n *       store: createHostedKnowledgeRetriever({ apiKey: process.env.MORDN_CHAT_KEY }),\n *       resolveNamespaces: () => [],\n *     },\n *   });\n *\n * No DATABASE_URL, no pgvector, no migrations — the hosted @mordn/chat-api\n * service owns ingestion + storage. The API key is an agent key issued from\n * the dashboard; the server resolves the tenant and agent from it.\n */\nimport 'server-only';\n\nexport {\n  createHostedKnowledgeRetriever,\n  type HostedKnowledgeOptions,\n} from './client';\n","/**\n * Hosted Knowledge retriever — a thin HTTP client over @mordn/chat-api's\n * `POST /v1/knowledge/query`. Same `Retriever` interface as the pgvector\n * default, so switching a consumer from BYO to hosted is a one-line change:\n *\n *   retrieval: {\n *     store: createHostedKnowledgeRetriever({ apiKey: process.env.MORDN_CHAT_KEY }),\n *     resolveNamespaces: () => [],\n *   }\n *\n * Identity: the `apiKey` is an agent key (mck_live_… / mck_test_…), issued\n * per-agent from the dashboard. The server resolves both the tenant AND the\n * agent from this single credential — there is no separate agentId to\n * provide. Every query is automatically scoped to this agent's knowledge base.\n */\n\nimport 'server-only';\nimport type {\n  Namespace,\n  QueryOptions,\n  Retriever,\n  RetrieverFactory,\n  RetrievedChunk,\n} from '../../knowledge/types';\nimport { withFetchTimeout, DEFAULT_HTTP_TIMEOUT_MS } from '../../http';\n\nconst DEFAULT_BASE_URL = 'https://api.mordn.com';\n\nexport interface HostedKnowledgeOptions {\n  /** Agent API key (mck_live_… / mck_test_…), issued per-agent from the\n   *  dashboard. Required. The server resolves the tenant and agent from this\n   *  key — no agentId is needed or accepted. Never sent to the client. */\n  apiKey: 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\nfunction normaliseChunk(raw: Record<string, unknown>): RetrievedChunk {\n  const src = (raw.source as { url?: string; title?: string }) ?? {};\n  return {\n    id: String(raw.id ?? ''),\n    text: String(raw.text ?? ''),\n    score: Number(raw.score ?? 0),\n    source: { url: src.url, title: src.title },\n    metadata: (raw.metadata as Record<string, unknown>) ?? {},\n  };\n}\n\nclass HostedKnowledgeRetriever implements Retriever {\n  private readonly base: string;\n  private readonly doFetch: typeof fetch;\n\n  constructor(\n    private readonly apiKey: string,\n    baseUrl: string,\n    fetchImpl: typeof fetch,\n    private readonly namespaces: ReadonlyArray<Namespace>,\n  ) {\n    this.base = baseUrl.replace(/\\/$/, '');\n    this.doFetch = fetchImpl;\n  }\n\n  async query(input: string, opts: QueryOptions = {}): Promise<RetrievedChunk[]> {\n    const res = await this.doFetch(`${this.base}/v1/knowledge/query`, {\n      method: 'POST',\n      headers: {\n        Authorization: `Bearer ${this.apiKey}`,\n        'Content-Type': 'application/json',\n        Accept: 'application/json',\n      },\n      body: JSON.stringify({\n        query: input,\n        topK: opts.topK,\n        minScore: opts.minScore,\n        // Forward resolved namespaces as a hint; the server enforces the\n        // tenant + agent scope from the API key.\n        namespaces: this.namespaces.length ? [...this.namespaces] : undefined,\n      }),\n    });\n    // Fail soft on the read path — a retrieval hiccup must not break a turn.\n    if (!res.ok) return [];\n    const data = (await res.json().catch(() => null)) as { chunks?: Record<string, unknown>[] } | null;\n    return (data?.chunks ?? []).map(normaliseChunk);\n  }\n}\n\n/**\n * Create a `RetrieverFactory` backed by the hosted @mordn/chat-api service.\n *\n *   createChatHandler({\n *     retrieval: {\n *       store: createHostedKnowledgeRetriever({ apiKey: process.env.MORDN_CHAT_KEY }),\n *       resolveNamespaces: () => [],\n *     },\n *   })\n *\n * The API key is an agent key — the server resolves the tenant and agent from\n * it and scopes every query to that agent's knowledge base automatically.\n */\nexport function createHostedKnowledgeRetriever(\n  options: HostedKnowledgeOptions,\n): RetrieverFactory {\n  if (!options.apiKey) throw new Error('[chat-widget] createHostedKnowledgeRetriever 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 (namespaces) =>\n    new HostedKnowledgeRetriever(options.apiKey, baseUrl, fetchImpl, namespaces);\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":";AAeA,OAAO;;;ACCP,OAAO;;;ACGP,OAAO;AAGA,IAAM,0BAA0B;AAahC,SAAS,iBAAiB,WAAyB,WAAiC;AACzF,MAAI,CAAC,aAAa,aAAa,EAAG,QAAO;AACzC,QAAM,UAAU,CACd,OACA,SACsB;AACtB,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,eAAe,MAAM;AAC3B,QAAI,cAAc;AAChB,UAAI,aAAa,QAAS,YAAW,MAAM;AAAA,UACtC,cAAa,iBAAiB,SAAS,MAAM,WAAW,MAAM,GAAG,EAAE,MAAM,KAAK,CAAC;AAAA,IACtF;AACA,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,SAAS;AAC5D,WAAO,QAAQ;AAAA,MACb,UAAU,OAAO,EAAE,OAAO,YAAY,GAAG,MAAM,QAAQ,WAAW,OAAO,CAAC;AAAA,IAC5E,EAAE,QAAQ,MAAM,aAAa,KAAK,CAAC;AAAA,EACrC;AACA,SAAO;AACT;;;AD3BA,IAAM,mBAAmB;AAezB,SAAS,eAAe,KAA8C;AACpE,QAAM,MAAO,IAAI,UAA+C,CAAC;AACjE,SAAO;AAAA,IACL,IAAI,OAAO,IAAI,MAAM,EAAE;AAAA,IACvB,MAAM,OAAO,IAAI,QAAQ,EAAE;AAAA,IAC3B,OAAO,OAAO,IAAI,SAAS,CAAC;AAAA,IAC5B,QAAQ,EAAE,KAAK,IAAI,KAAK,OAAO,IAAI,MAAM;AAAA,IACzC,UAAW,IAAI,YAAwC,CAAC;AAAA,EAC1D;AACF;AAEA,IAAM,2BAAN,MAAoD;AAAA,EAIlD,YACmB,QACjB,SACA,WACiB,YACjB;AAJiB;AAGA;AAEjB,SAAK,OAAO,QAAQ,QAAQ,OAAO,EAAE;AACrC,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,MAAM,MAAM,OAAe,OAAqB,CAAC,GAA8B;AAC7E,UAAM,MAAM,MAAM,KAAK,QAAQ,GAAG,KAAK,IAAI,uBAAuB;AAAA,MAChE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,eAAe,UAAU,KAAK,MAAM;AAAA,QACpC,gBAAgB;AAAA,QAChB,QAAQ;AAAA,MACV;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACnB,OAAO;AAAA,QACP,MAAM,KAAK;AAAA,QACX,UAAU,KAAK;AAAA;AAAA;AAAA,QAGf,YAAY,KAAK,WAAW,SAAS,CAAC,GAAG,KAAK,UAAU,IAAI;AAAA,MAC9D,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,IAAI,GAAI,QAAO,CAAC;AACrB,UAAM,OAAQ,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI;AAC/C,YAAQ,MAAM,UAAU,CAAC,GAAG,IAAI,cAAc;AAAA,EAChD;AACF;AAeO,SAAS,+BACd,SACkB;AAClB,MAAI,CAAC,QAAQ,OAAQ,OAAM,IAAI,MAAM,iEAAiE;AACtG,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,YAAY,iBAAiB,QAAQ,SAAS,OAAO,QAAQ,aAAa,uBAAuB;AACvG,SAAO,CAAC,eACN,IAAI,yBAAyB,QAAQ,QAAQ,SAAS,WAAW,UAAU;AAC/E;","names":[]}