{"version":3,"sources":["../../../../src/server/stores/knowledge-drizzle/index.ts","../../../../src/server/stores/knowledge-drizzle/store.ts","../../../../src/server/knowledge/types.ts","../../../../src/server/stores/drizzle/client.ts","../../../../src/server/stores/drizzle/schema.ts","../../../../src/server/stores/knowledge-drizzle/schema.ts"],"sourcesContent":["/**\n * Default Postgres + pgvector KnowledgeStore — public entry.\n *\n * Imported via `@mordn/chat-widget/server/knowledge/drizzle` so a BYO consumer\n * who passes their own retriever never pulls `postgres`/`drizzle-orm` into their\n * bundle. Two factories, split by trust:\n *\n *   // READ-ONLY — wire into the chat handler:\n *   import { createKnowledgeDrizzleRetriever } from '@mordn/chat-widget/server/knowledge/drizzle';\n *   createChatHandler({ retrieval: { store: createKnowledgeDrizzleRetriever({ embedder }), ... } });\n *\n *   // READ+WRITE — admin/ingestion ONLY (never given to the handler):\n *   import { createKnowledgeDrizzleStore } from '@mordn/chat-widget/server/knowledge/drizzle';\n *   const store = createKnowledgeDrizzleStore({ embedder })(`agent:${agentId}`);\n *\n * The schema is exported so `drizzle-kit` can generate base migrations; the\n * shipped 0001_knowledge.sql adds the pgvector column, the generated tsvector,\n * and the HNSW/GIN indexes (which drizzle-kit can't model).\n */\nimport 'server-only';\n\nexport {\n  createKnowledgeDrizzleRetriever,\n  createKnowledgeDrizzleStore,\n  type PgVectorKnowledgeOptions,\n} from './store';\nexport { getDrizzleDb, type DrizzleClientOptions, type DrizzleDb } from '../drizzle/client';\nexport * as schema from './schema';\nexport { EMBED_DIM, vector } from './schema';\nexport type {\n  KnowledgeSourceRow,\n  NewKnowledgeSourceRow,\n  KnowledgeChunkRow,\n  NewKnowledgeChunkRow,\n} from './schema';\n","/**\n * Default KnowledgeStore implementation — Postgres + pgvector via Drizzle.\n *\n * One implementation of the `KnowledgeStore`/`Retriever` interfaces — the\n * interfaces, not this file, are the contract. It reuses the existing\n * `getDrizzleDb` pool (no second connection manager) and ships its own schema +\n * SQL migration (`CREATE EXTENSION vector`, the generated tsvector column, and\n * the HNSW/GIN indexes).\n *\n * Security invariants upheld here (mirroring the chat store):\n *   • A `Retriever`/`KnowledgeStore` is bound to namespace(s) at construction.\n *   • `query` is hard-fenced to those namespaces — there is no namespace\n *     parameter, so a foreign namespace is unrepresentable.\n *   • Writes assert the bound namespace (`NamespaceAccessError` → 403).\n *   • `upsert` is idempotent on (namespace, source, chunkIndex), short-circuits\n *     unchanged sources by contentHash, and deletes the orphaned tail when a\n *     source shrinks.\n */\n\nimport 'server-only';\nimport { createHash } from 'node:crypto';\nimport { and, eq, inArray, sql } from 'drizzle-orm';\n\nimport {\n  NamespaceAccessError,\n  type Embedder,\n  type KnowledgeDoc,\n  type KnowledgeStore,\n  type KnowledgeStoreFactory,\n  type Namespace,\n  type QueryOptions,\n  type RetrievedChunk,\n  type RetrieverFactory,\n  type SourceInfo,\n  type UpsertResult,\n} from '../../knowledge/types';\nimport { getDrizzleDb, type DrizzleClientOptions, type DrizzleDb } from '../drizzle/client';\nimport {\n  EMBED_DIM,\n  knowledgeChunks,\n  knowledgeSources,\n  type KnowledgeSourceRow,\n} from './schema';\n\nconst TOPK_CEILING = 20;\nconst DEFAULT_TOPK = 5;\nconst DEFAULT_MIN_SCORE = 0.2;\n\n/** Serialise an embedding to the pgvector literal form for a raw SQL cast. */\nfunction vectorLiteral(embedding: number[]): string {\n  return `[${embedding.join(',')}]`;\n}\n\nclass PgVectorKnowledgeStore implements KnowledgeStore {\n  constructor(\n    public readonly namespaces: ReadonlyArray<Namespace>,\n    private readonly db: DrizzleDb,\n    private readonly embedder: Embedder,\n  ) {\n    if (embedder.dimensions !== EMBED_DIM) {\n      // Fail loud at construction — a dimension mismatch silently returns\n      // garbage results (and breaks inserts).\n      throw new Error(\n        `[chat-widget] embedder dim ${embedder.dimensions} != column ${EMBED_DIM}. ` +\n          'Re-migrate the vector column or pass a matching embedder.',\n      );\n    }\n  }\n\n  /** The single namespace a write-bound store targets (first/only entry). */\n  private writeNamespace(): Namespace {\n    const ns = this.namespaces[0];\n    if (!ns) throw new NamespaceAccessError('<none>');\n    return ns;\n  }\n\n  async query(input: string, opts: QueryOptions = {}): Promise<RetrievedChunk[]> {\n    if (this.namespaces.length === 0) return [];\n    const topK = Math.min(Math.max(opts.topK ?? DEFAULT_TOPK, 1), TOPK_CEILING);\n    const minScore = opts.minScore ?? DEFAULT_MIN_SCORE;\n    const w = opts.vectorWeight ?? 1;\n\n    // Query path → use the embedder's query-framing when it offers one\n    // (RETRIEVAL_QUERY for asymmetric retrieval), else fall back to embed().\n    const embedFn = this.embedder.embedQuery ?? this.embedder.embed;\n    const [embedding] = await embedFn.call(this.embedder, [input]);\n    if (!embedding) return [];\n    const vec = sql.raw(`'${vectorLiteral(embedding)}'::vector`);\n\n    // Hard namespace fence — the ONLY namespaces are this.namespaces. A caller\n    // cannot widen it; there is no namespace parameter on query().\n    const nsList = sql.raw(\n      `(${this.namespaces.map((n) => `'${n.replace(/'/g, \"''\")}'`).join(',')})`,\n    );\n\n    // Optional metadata equality filter (JSONB containment).\n    const metaFilter = opts.filter\n      ? sql`AND metadata @> ${JSON.stringify(opts.filter)}::jsonb`\n      : sql``;\n\n    // Hybrid score: w * (1 - cosine_distance) + (1-w) * ts_rank.\n    // Pure vector when w = 1. The generated `tsv` column powers the lexical leg.\n    const rows = await this.db.execute(sql`\n      SELECT id, namespace, source, title, chunk_index, content, metadata,\n             (${w} * (1 - (embedding <=> ${vec}))\n              + ${1 - w} * COALESCE(ts_rank(tsv, plainto_tsquery('english', ${input})), 0)) AS score\n      FROM ${knowledgeChunks}\n      WHERE namespace IN ${nsList} ${metaFilter}\n      ORDER BY score DESC\n      LIMIT ${topK}\n    `);\n\n    const list = (rows as unknown as { rows?: unknown[] }).rows ?? (rows as unknown as unknown[]);\n    return (list as Array<Record<string, unknown>>)\n      .map((r): RetrievedChunk => {\n        const source = String(r.source ?? '');\n        const title = (r.title as string) || source;\n        const isUrl = /^https?:\\/\\//.test(source);\n        return {\n          id: String(r.id ?? ''),\n          text: String(r.content ?? ''),\n          score: Number(r.score ?? 0),\n          source: { url: isUrl ? source : undefined, title },\n          metadata: {\n            ...((r.metadata as Record<string, unknown>) ?? {}),\n            namespace: r.namespace,\n            chunkIndex: r.chunk_index,\n            sourceRef: source,\n          },\n        };\n      })\n      .filter((c) => c.score >= minScore);\n  }\n\n  async upsert(docs: KnowledgeDoc[]): Promise<UpsertResult> {\n    const namespace = this.writeNamespace();\n    if (docs.length === 0) return { upsertedChunks: 0, skippedSources: 0, deletedOrphans: 0 };\n\n    // Group by source; one source = one resync unit. Order within a source is\n    // the array order → chunkIndex.\n    const bySource = new Map<string, KnowledgeDoc[]>();\n    for (const d of docs) {\n      const arr = bySource.get(d.source);\n      if (arr) arr.push(d);\n      else bySource.set(d.source, [d]);\n    }\n\n    let upsertedChunks = 0;\n    let skippedSources = 0;\n    let deletedOrphans = 0;\n\n    for (const [source, chunks] of bySource) {\n      const contentHash =\n        (chunks[0].metadata?.contentHash as string | undefined) ??\n        deriveHash(chunks.map((c) => c.text).join('\\n'));\n\n      const existing = await this.db\n        .select({ h: knowledgeSources.contentHash })\n        .from(knowledgeSources)\n        .where(and(eq(knowledgeSources.namespace, namespace), eq(knowledgeSources.source, source)))\n        .limit(1);\n\n      // INCREMENTAL RESYNC short-circuit: unchanged content → skip entirely.\n      if (existing[0]?.h === contentHash) {\n        skippedSources++;\n        continue;\n      }\n\n      // Embed all chunks for this source in one batched call.\n      const vectors = await this.embedder.embed(chunks.map((c) => c.text));\n      const title = chunks[0].title ?? source;\n      const values = chunks.map((c, i) => ({\n        id: c.id ?? `${namespace}::${source}::${i}`,\n        namespace,\n        source,\n        title: c.title ?? title,\n        chunkIndex: i,\n        content: c.text,\n        embedding: vectors[i],\n        contentHash,\n        metadata: c.metadata ?? {},\n      }));\n\n      await this.db\n        .insert(knowledgeChunks)\n        .values(values)\n        .onConflictDoUpdate({\n          target: [knowledgeChunks.namespace, knowledgeChunks.source, knowledgeChunks.chunkIndex],\n          set: {\n            content: sql`excluded.content`,\n            embedding: sql`excluded.embedding`,\n            contentHash: sql`excluded.content_hash`,\n            title: sql`excluded.title`,\n            metadata: sql`excluded.metadata`,\n          },\n        });\n      upsertedChunks += values.length;\n\n      // Delete orphaned tail if the doc shrank (chunkIndex >= newCount).\n      const del = await this.db\n        .delete(knowledgeChunks)\n        .where(\n          and(\n            eq(knowledgeChunks.namespace, namespace),\n            eq(knowledgeChunks.source, source),\n            sql`${knowledgeChunks.chunkIndex} >= ${chunks.length}`,\n          ),\n        )\n        .returning({ id: knowledgeChunks.id });\n      deletedOrphans += del.length;\n\n      await this.db\n        .insert(knowledgeSources)\n        .values({\n          id: `${namespace}::${source}`,\n          namespace,\n          source,\n          title,\n          status: 'ready',\n          contentHash,\n          chunkCount: chunks.length,\n          metadata: (chunks[0].metadata as Record<string, unknown>) ?? {},\n          updatedAt: new Date(),\n        })\n        .onConflictDoUpdate({\n          target: knowledgeSources.id,\n          set: {\n            contentHash,\n            chunkCount: chunks.length,\n            title,\n            status: 'ready',\n            error: null,\n            updatedAt: new Date(),\n          },\n        });\n    }\n\n    return { upsertedChunks, skippedSources, deletedOrphans };\n  }\n\n  async delete(by: { source?: string; ids?: string[] }): Promise<void> {\n    const namespace = this.writeNamespace();\n    if (by.ids && by.ids.length) {\n      await this.db\n        .delete(knowledgeChunks)\n        .where(and(eq(knowledgeChunks.namespace, namespace), inArray(knowledgeChunks.id, by.ids)));\n    }\n    if (by.source) {\n      await this.db\n        .delete(knowledgeChunks)\n        .where(and(eq(knowledgeChunks.namespace, namespace), eq(knowledgeChunks.source, by.source)));\n      await this.db\n        .delete(knowledgeSources)\n        .where(and(eq(knowledgeSources.namespace, namespace), eq(knowledgeSources.source, by.source)));\n    }\n  }\n\n  async listSources(): Promise<SourceInfo[]> {\n    const namespace = this.writeNamespace();\n    const rows = await this.db\n      .select()\n      .from(knowledgeSources)\n      .where(eq(knowledgeSources.namespace, namespace));\n    return rows.map((r: KnowledgeSourceRow) => ({\n      id: r.id,\n      source: r.source,\n      status: r.status,\n      chunkCount: r.chunkCount,\n      updatedAt: r.updatedAt.toISOString(),\n    }));\n  }\n\n  async purge(): Promise<void> {\n    const namespace = this.writeNamespace();\n    await this.db.delete(knowledgeChunks).where(eq(knowledgeChunks.namespace, namespace));\n    await this.db.delete(knowledgeSources).where(eq(knowledgeSources.namespace, namespace));\n  }\n}\n\n/** sha256 of text, for the contentHash fallback when ingest didn't supply one. */\nfunction deriveHash(text: string): string {\n  return createHash('sha256').update(text).digest('hex');\n}\n\nexport interface PgVectorKnowledgeOptions extends DrizzleClientOptions {\n  /** Embedder whose `dimensions` MUST equal the vector column width (1536). */\n  embedder: Embedder;\n}\n\n/**\n * Read-only factory for the handler. Pass to\n * `createChatHandler({ retrieval: { store: createKnowledgeDrizzleRetriever({ embedder }) } })`.\n */\nexport function createKnowledgeDrizzleRetriever(\n  opts: PgVectorKnowledgeOptions,\n): RetrieverFactory {\n  const db = getDrizzleDb(opts);\n  return (namespaces) => new PgVectorKnowledgeStore(namespaces, db, opts.embedder);\n}\n\n/**\n * Read+write factory for the ingestion/admin module ONLY. Bound to ONE\n * namespace; never passed to `createChatHandler`.\n *\n *   const store = createKnowledgeDrizzleStore({ embedder })(`agent:${agentId}`);\n *   await ingest({ store, namespace: `agent:${agentId}`, sources, embedder });\n */\nexport function createKnowledgeDrizzleStore(\n  opts: PgVectorKnowledgeOptions,\n): KnowledgeStoreFactory {\n  const db = getDrizzleDb(opts);\n  return (namespace) => new PgVectorKnowledgeStore([namespace], db, opts.embedder);\n}\n","/**\n * Knowledge / RAG contracts — the read/write surfaces for retrieval-augmented\n * generation, plus the ingestion vocabulary.\n *\n * This is the THIRD pluggable backend family of the widget, beside `ChatStore`\n * (conversations) and `StorageAdapter` (attachments). It owns *developer-curated,\n * shared* knowledge: the docs/pages/files an agent answers FROM. It is NOT\n * `MemoryAdapter` (per-user, generated) and NOT the conversation history.\n *\n * ──────────────────────────────────────────────────────────────────────────\n * The security model is in the SHAPE of these interfaces, not in their callers.\n * ──────────────────────────────────────────────────────────────────────────\n *\n * The contract is deliberately split by **trust level**:\n *\n *   • `Retriever`      — the READ surface. Safe to construct in the chat\n *                        request path (reachable by end users). It can only\n *                        `query`, and it is hard-bound to a set of namespaces at\n *                        construction. There is NO namespace parameter on\n *                        `query`, so a foreign namespace is unrepresentable —\n *                        the same IDOR defence `ChatStore`'s bound `userId`\n *                        gives. It has no `upsert`/`delete`.\n *\n *   • `KnowledgeStore` — the WRITE surface (a superset of read). Used ONLY by\n *                        the ingestion/admin module. Because `createChatHandler`\n *                        is only ever handed a `RetrieverFactory`, there is no\n *                        code path through which an end user can mutate the KB.\n *\n * A BYO backend implements both against one storage; the package wires the\n * read one into the handler and the write one into `ingest()`.\n *\n * Guarded by `server-only`: these types reference server concerns and the\n * implementations hold secrets (DB URL, embedding-provider keys).\n */\n\nimport 'server-only';\n\n/**\n * A namespace is the isolation boundary for retrieval — the RAG analogue of\n * `ChatStore`'s bound `userId`. Everything a Retriever/Store touches is fenced\n * inside one or more namespaces, and the namespace value is ALWAYS derived\n * server-side (from agentId / tenantId / verified userId), never from a request\n * body.\n *\n * Convention (opaque to the store, meaningful to the host):\n *   agent:<agentId>            shared agent KB (everyone chatting this agent sees it)\n *   tenant:<tenantId>          org-wide KB\n *   user:<userId>:<agentId>    private \"chat with my PDF\" docs for one user+agent\n */\nexport type Namespace = string;\n\n/**\n * Arbitrary, store-filterable metadata stamped on every chunk.\n *\n * Values are scalars ONLY (`string | number | boolean | null`) — a store maps\n * these onto filterable columns / JSONB, so arrays/objects are not\n * representable. Docs-aware ingestion (`chunkMarkdown`) stamps two keys the\n * hosted retriever reads back to build deep-link citations, and both obey this\n * scalar rule (see the CROSS-REPO contract in DOCS_CONTRACT §3):\n *   • `anchor?: string`      — URL-fragment-ready slug of the nearest heading.\n *   • `headingPath?: string` — the section breadcrumb JOINED with \" › \" (a\n *                              STRING, never an array — arrays can't live here).\n */\nexport type ChunkMetadata = Record<string, string | number | boolean | null>;\n\n// ── Contract types named per the build contract ──────────────────────────────\n\n/**\n * A retrieved chunk + its similarity score, ready to inject + cite. Shaped to\n * map onto an AI SDK `source-url` part for the existing citations UI.\n */\nexport interface RetrievedChunk {\n  /** Stable chunk id (`<namespace>::<source>::<chunkIndex>` in the default store). */\n  id: string;\n  /** The chunk's text. */\n  text: string;\n  /** Similarity in [0,1] (cosine or provider-native, normalised by the store). */\n  score: number;\n  /** Where it came from — drives the citation link + label. */\n  source: { url?: string; title?: string };\n  /** Filterable/echo metadata (lang, section, chunkIndex, namespace, …). */\n  metadata?: Record<string, unknown>;\n}\n\n/** Knobs for a single retrieval call. */\nexport interface QueryOptions {\n  /** Max chunks to return. Default 5; the store clamps to a ceiling (20). */\n  topK?: number;\n  /** Drop chunks below this similarity. Default 0.2 to cut noise. */\n  minScore?: number;\n  /**\n   * Hybrid weighting: 1 = pure vector, 0 = pure lexical (tsvector). Default 1.\n   * Stores that don't support lexical ignore it. Hybrid massively helps exact\n   * terms (product names, error codes) that embeddings blur.\n   */\n  vectorWeight?: number;\n  /** Equality filter over chunk metadata (e.g. `{ lang: 'en' }`). */\n  filter?: ChunkMetadata;\n}\n\n/**\n * A unit of knowledge as the host hands it in for upsert (already chunked +\n * cleaned by the ingestion pipeline; pre-embedding).\n */\nexport interface KnowledgeDoc {\n  /** Optional explicit id; the store derives a stable one if omitted. */\n  id?: string;\n  /** A single chunk's text. */\n  text: string;\n  /**\n   * Stable logical identity of the source this chunk came from — a URL, a file\n   * key, a doc slug. Re-ingesting the same `source` REPLACES its chunks\n   * (incremental re-sync), so `source` is the dedupe/delete unit, like a row key.\n   */\n  source: string;\n  /** Human title for citations (falls back to `source`). */\n  title?: string;\n  /**\n   * Filterable metadata (lang, section, chunkIndex, contentHash, …). For\n   * docs-aware ingestion this also carries `anchor` (nearest-heading slug) and\n   * `headingPath` (breadcrumb joined by \" › \") so retrieval can deep-link to the\n   * exact section — see the cross-repo contract on `ChunkMetadata` above.\n   */\n  metadata?: Record<string, unknown>;\n}\n\n/** A known source in a namespace (admin listing / re-sync planning). */\nexport interface SourceInfo {\n  /** Stable source row id (`<namespace>::<source>` in the default store). */\n  id: string;\n  /** The logical source (URL / file key / slug). */\n  source: string;\n  /** Ingestion status: 'ready' once chunks are live (extensible). */\n  status: string;\n  /** How many chunks this source currently has. */\n  chunkCount: number;\n  /** ISO timestamp of the last upsert. */\n  updatedAt: string;\n}\n\n/** Result of an `upsert` — surfaces the incremental-resync accounting. */\nexport interface UpsertResult {\n  upsertedChunks: number;\n  /** Sources skipped because their contentHash was unchanged. */\n  skippedSources: number;\n  /** Orphaned tail chunks deleted because a source shrank. */\n  deletedOrphans: number;\n}\n\n// ── READ surface — safe in the chat request path ─────────────────────────────\n\n/**\n * The read surface. Construct via a `RetrieverFactory` bound to server-resolved\n * namespaces; `query` is hard-fenced to those namespaces. No namespace\n * parameter ⇒ cross-tenant reads are unrepresentable.\n */\nexport interface Retriever {\n  /**\n   * Semantic (+ optionally lexical) search. The store embeds the query string\n   * via its configured embedder. MUST restrict results to the namespaces this\n   * instance was constructed for — there is no parameter through which a foreign\n   * namespace can enter.\n   */\n  query(input: string, opts?: QueryOptions): Promise<RetrievedChunk[]>;\n}\n\n// ── WRITE surface — ingestion / admin ONLY ───────────────────────────────────\n\n/**\n * The full read+write store. Intended for the admin/ingestion module only; it\n * is NOT passed to `createChatHandler`. Keeping the write factory out of the\n * handler's option bag is a structural guarantee that the chat path can't write.\n */\nexport interface KnowledgeStore extends Retriever {\n  /**\n   * Insert-or-replace chunks.\n   *  - Idempotent on (namespace, source, chunkIndex).\n   *  - If every doc for a `source` carries a contentHash already present for\n   *    that source, the store no-ops that source (incremental resync).\n   *  - Replacing a source with FEWER chunks deletes the orphaned tail\n   *    (chunkIndex >= newCount) so shrinking a doc doesn't leave stale chunks.\n   * Embeds internally (delegates to the configured embedder).\n   */\n  upsert(docs: KnowledgeDoc[]): Promise<UpsertResult>;\n\n  /** Delete chunks by source(s) or explicit ids. Idempotent. */\n  delete(by: { source?: string; ids?: string[] }): Promise<void>;\n\n  /** List sources in this store's namespace (admin dashboard, resync diffing). */\n  listSources(): Promise<SourceInfo[]>;\n\n  /** Wipe the entire namespace (e.g. user deletes their private KB / GDPR). */\n  purge(): Promise<void>;\n}\n\n/**\n * Constructs a `Retriever` bound to an EXPLICIT, server-resolved set of\n * namespaces. Called from the handler AFTER auth, with namespaces computed from\n * the verified ctx (agentId/tenantId/userId) — NEVER from the request body.\n */\nexport type RetrieverFactory = (namespaces: Namespace[]) => Retriever;\n\n/**\n * Constructs the full read+write store, bound to ONE namespace (the write/\n * delete/list/purge surface operates within it). Intended for the\n * admin/ingestion module only; it is never passed to `createChatHandler`.\n */\nexport type KnowledgeStoreFactory = (namespace: Namespace) => KnowledgeStore;\n\n/**\n * The embedding seam, so the store stays model-agnostic. The default\n * implementation is Google Gemini (`gemini-embedding-2`, 1536-dim, L2-normalized)\n * over REST; BYO consumers can wrap any AI SDK model via `createEmbedder`. The\n * host controls the model + dimension, which must match the vector column width.\n */\nexport interface Embedder {\n  /** MUST equal the vector column width (e.g. 1536). */\n  readonly dimensions: number;\n  /**\n   * Embed a batch of DOCUMENT chunks (order-preserving). The hot path for\n   * ingestion — providers that support retrieval task types should frame these\n   * as `RETRIEVAL_DOCUMENT`.\n   */\n  embed(texts: string[]): Promise<number[][]>;\n  /**\n   * OPTIONAL query-side embedding (order-preserving). Lets task-type-aware\n   * providers frame inputs as `RETRIEVAL_QUERY` for asymmetric retrieval. The\n   * store uses this for the query path when present and falls back to `embed`\n   * otherwise — so an embedder that only implements `embed` still works.\n   */\n  embedQuery?(texts: string[]): Promise<number[][]>;\n}\n\n/**\n * Thrown by write/list methods when the target namespace is out of scope for\n * the store. Callers map this to HTTP 403 (admin path). Reads never throw — they\n * return `[]` — so existence of another namespace can't be probed.\n */\nexport class NamespaceAccessError extends Error {\n  constructor(public readonly namespace: Namespace) {\n    super(`Namespace ${namespace} is out of scope for this store`);\n    this.name = 'NamespaceAccessError';\n  }\n}\n\n// ── Ingestion (admin/server only) ────────────────────────────────────────────\n\n/** A source to ingest. `crawl`/`sitemap`/`url`/`llms` are SSRF-guarded at load time. */\nexport type IngestSource =\n  | { type: 'url'; url: string; title?: string }\n  | { type: 'sitemap'; url: string; limit?: number }\n  | { type: 'crawl'; url: string; depth?: number; maxPages?: number; sameOriginOnly?: boolean }\n  | { type: 'file'; path?: string; fileKey?: string; filename?: string; mediaType?: string }\n  | { type: 'text'; text: string; title?: string }\n  /**\n   * An `llms.txt` index (the emerging docs-site ⇄ AI handshake). Fetches the\n   * file, parses its markdown link list (`- [Title](href)` items, optional\n   * `: description`), resolves relative hrefs, dedupes, and expands to one leaf\n   * per linked doc (usually `.md` → raw-markdown passthrough). An `llms-full.txt`\n   * style file with no links is ingested as ONE markdown doc. Capped at\n   * `limit ?? crawl.maxPages`.\n   */\n  | { type: 'llms'; url: string; limit?: number };\n\n/** Progress event surfaced by `ingest`'s `onProgress`. */\nexport interface IngestProgress {\n  /** done count of `total` units processed so far. */\n  done: number;\n  total: number;\n  /** Coarse stage label: 'fetch' | 'extract' | 'chunk' | 'embed' | 'upsert' | 'done' | 'error'. */\n  stage: string;\n  /** The source currently being processed (when applicable). */\n  source?: string;\n  /** Optional human message (e.g. an error string). */\n  message?: string;\n}\n\n/** Tunables for `ingest`. */\nexport interface IngestOptions {\n  /** Target chunk size in TOKENS (approx). Default 512. */\n  chunkSize?: number;\n  /** Overlap in tokens to preserve context across boundaries. Default 64. */\n  overlap?: number;\n  /** Bounded concurrency for fetch/embed. Default 4. */\n  concurrency?: number;\n  /**\n   * Crawl/SSRF safety rails. Defaults: same-origin only, maxPages 50, depth 2,\n   * 10s request timeout. `allowDomains` further restricts host matching.\n   */\n  crawl?: {\n    allowDomains?: string[];\n    maxPages?: number;\n    maxDepth?: number;\n    requestTimeoutMs?: number;\n    userAgent?: string;\n    sameOriginOnly?: boolean;\n  };\n  /**\n   * Docs-aware ingestion. When `true` (default), markdown sources (mediaType\n   * includes `markdown`, a `.md`/`.mdx`/`.markdown` pathname, or HTML converted\n   * via `htmlToMarkdown`) are routed through the heading-aware `chunkMarkdown`,\n   * which preserves section structure and stamps `anchor` + `headingPath` for\n   * deep-link citations. Set `false` to force the legacy plain path\n   * (`htmlToCleanText` + `chunkText`) for every source — an escape hatch for\n   * corpora that aren't docs.\n   */\n  docsMode?: boolean;\n  /**\n   * When expanding a `sitemap` or `crawl` source, first probe\n   * `origin + \"/llms.txt\"`; if it returns 200 with ≥1 parsed link, ingest those\n   * curated markdown leaves INSTEAD of the sitemap/crawl expansion (and surface\n   * a progress message saying so). Default `true`. Set `false` to always use the\n   * sitemap/crawl expansion. Probe failures are silent (normal expansion runs).\n   */\n  preferLlmsTxt?: boolean;\n  /** Progress callback — drives a dashboard progress bar / SSE. */\n  onProgress?: (p: IngestProgress) => void;\n}\n\n/** Summary returned by `ingest` (also see per-source errors in the report). */\nexport interface IngestReport {\n  sources: number;\n  chunks: number;\n  /** Sources skipped because their contentHash was unchanged. */\n  skipped: number;\n  /** Orphaned tail chunks deleted because a source shrank. */\n  deleted: number;\n  /** Per-source failures (loader/extract/embed) — ingest never throws on one. */\n  errors: { source: string; error: string }[];\n  durationMs: number;\n}\n","/**\n * Postgres connection for the default Drizzle store.\n *\n * Carries forward the connection hygiene the original package got right:\n *   • cache the client on globalThis so Next.js dev HMR doesn't leak a fresh\n *     pool on every reload (which eventually exhausts Supabase's pooler),\n *   • `prepare: false` for the Supabase transaction-mode pooler,\n *   • a small, bounded pool.\n *\n * The connection string is read from `DATABASE_URL` by default but can be\n * passed explicitly so a host app with multiple databases stays in control.\n */\n\nimport 'server-only';\nimport { drizzle } from 'drizzle-orm/postgres-js';\nimport postgres from 'postgres';\nimport * as schema from './schema';\n\nexport type DrizzleDb = ReturnType<typeof drizzle<typeof schema>>;\n\nexport interface DrizzleClientOptions {\n  /** Postgres connection string. Defaults to `process.env.DATABASE_URL`. */\n  connectionString?: string;\n  /** Max pool connections. Defaults to 5 (dev-friendly; raise in prod). */\n  max?: number;\n}\n\nconst globalForDb = globalThis as unknown as {\n  __mordnChatWidgetDb?: Map<string, DrizzleDb>;\n};\n\n/**\n * Get (or lazily create + cache) a Drizzle db for a connection string. Cached\n * by connection string so multiple stores over the same DB share one pool.\n */\nexport function getDrizzleDb(options: DrizzleClientOptions = {}): DrizzleDb {\n  const connectionString = options.connectionString ?? process.env.DATABASE_URL;\n  if (!connectionString) {\n    throw new Error(\n      '[chat-widget] DATABASE_URL is not set and no connectionString was ' +\n        'provided to the default Drizzle store.',\n    );\n  }\n\n  const cache = (globalForDb.__mordnChatWidgetDb ??= new Map());\n  const cached = cache.get(connectionString);\n  if (cached) return cached;\n\n  const client = postgres(connectionString, {\n    prepare: false, // required for Supabase transaction-mode pooler\n    max: options.max ?? 5,\n    idle_timeout: 20,\n    connect_timeout: 10,\n  });\n  const db = drizzle(client, { schema });\n\n  // Only cache in non-prod: in prod each server instance loads the module once\n  // so caching buys nothing and would pin a pool across the process lifetime.\n  if (process.env.NODE_ENV !== 'production') cache.set(connectionString, db);\n  return db;\n}\n","/**\n * Drizzle schema for the default ChatStore (v2, parts-first).\n *\n * This is the schema the *default* store uses. A BYO store may use any schema\n * it likes — this one is not part of the public contract, the `ChatStore`\n * interface is. It's exported so consumers on the default path can run\n * `drizzle-kit` against it and so the migration can reference it.\n *\n * What changed from v0.7.1\n * ------------------------\n * The old schema stored a flattened `content: text` as the apparent source of\n * truth and tucked the real AI SDK `parts` into a `metadata` jsonb blob. That\n * inverted the actual authority — `parts` (text + reasoning + tool calls +\n * sources + files) is what the AI SDK round-trips and what rendering needs;\n * `content` was a lossy shadow.\n *\n * v2 makes that authority explicit:\n *   • `parts`  — jsonb NOT NULL — the canonical AI SDK message parts. Source\n *                of truth for rendering and model replay.\n *   • `text`   — text — a denormalised projection of the text parts, for\n *                cheap previews / titles / search. Never authoritative.\n *\n * A backfill migration populates these from the old columns so existing\n * installs upgrade without data loss (see migrations/).\n */\n\nimport { pgTable, text, timestamp, jsonb, index } from 'drizzle-orm/pg-core';\nimport type { UIMessage } from 'ai';\n\nexport const conversations = pgTable(\n  'chat_conversations',\n  {\n    id: text('id').primaryKey(),\n    userId: text('user_id').notNull(),\n    title: text('title').notNull().default('New Chat'),\n    /** Free-form host-app metadata. Never read by the core. */\n    metadata: jsonb('metadata').$type<Record<string, unknown>>(),\n    createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),\n    updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),\n  },\n  (table) => [\n    // Drives the history list (WHERE user_id = ? ORDER BY updated_at DESC).\n    index('chat_conversations_user_updated_idx').on(table.userId, table.updatedAt),\n  ],\n);\n\nexport const messages = pgTable(\n  'chat_messages',\n  {\n    id: text('id').primaryKey(),\n    conversationId: text('conversation_id')\n      .notNull()\n      .references(() => conversations.id, { onDelete: 'cascade' }),\n    role: text('role').notNull().$type<'user' | 'assistant' | 'system'>(),\n    /** Canonical AI SDK parts — source of truth. */\n    parts: jsonb('parts').$type<UIMessage['parts']>().notNull(),\n    /** Denormalised plain-text projection for previews/search. */\n    text: text('text').notNull().default(''),\n    /** Model that produced this message (assistant turns). */\n    model: text('model'),\n    createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),\n  },\n  (table) => [\n    // Drives history load (WHERE conversation_id = ? ORDER BY created_at).\n    index('chat_messages_conversation_created_idx').on(table.conversationId, table.createdAt),\n  ],\n);\n\nexport type ConversationRow = typeof conversations.$inferSelect;\nexport type NewConversationRow = typeof conversations.$inferInsert;\nexport type MessageRow = typeof messages.$inferSelect;\nexport type NewMessageRow = typeof messages.$inferInsert;\n","/**\n * Drizzle schema for the default Postgres + pgvector KnowledgeStore.\n *\n * As with the chat schema, this is NOT the public contract — the\n * `KnowledgeStore`/`Retriever` interfaces are. It's exported so consumers on the\n * default path can run `drizzle-kit` against it and so the shipped SQL migration\n * can reference it.\n *\n * Two tables:\n *   • knowledge_sources — one row per logical source, holding `contentHash` for\n *     cheap resync diffing (is this source unchanged? → one indexed lookup).\n *   • knowledge_chunks  — the vectors + a generated tsvector for hybrid lexical\n *     search.\n *\n * The `vector(1536)` column and the generated `tsv` column + their indexes are\n * created in the shipped SQL migration (drizzle-kit can't model pgvector or a\n * GENERATED tsvector column directly). The custom `vector` type below lets the\n * store SELECT/INSERT the column through Drizzle.\n */\n\nimport {\n  pgTable,\n  text,\n  integer,\n  timestamp,\n  jsonb,\n  index,\n  uniqueIndex,\n  customType,\n} from 'drizzle-orm/pg-core';\n\n/**\n * pgvector column type. Dimension is fixed at migration time and MUST match the\n * embedder. We serialise to the `'[1,2,3]'` text form pgvector accepts, and\n * parse the same form back.\n */\nexport const vector = (name: string, dim: number) =>\n  customType<{ data: number[]; driverData: string }>({\n    dataType() {\n      return `vector(${dim})`;\n    },\n    toDriver(value: number[]): string {\n      return `[${value.join(',')}]`;\n    },\n    fromDriver(value: string): number[] {\n      // pgvector returns e.g. \"[0.1,0.2,...]\"; tolerate already-parsed arrays.\n      if (Array.isArray(value)) return value as unknown as number[];\n      return value.slice(1, -1).split(',').map(Number);\n    },\n  })(name);\n\n/**\n * Default embedding dimension (Gemini gemini-embedding-2 at output\n * dimensionality 1536, L2-normalized). Override in a host migration if you pick\n * a different model — the column width is the source of truth and must equal the\n * embedder's `dimensions`.\n */\nexport const EMBED_DIM = 1536;\n\nexport const knowledgeSources = pgTable(\n  'knowledge_sources',\n  {\n    /** `<namespace>::<source>` — stable per (namespace, source). */\n    id: text('id').primaryKey(),\n    /** Isolation boundary (agent:… / tenant:… / user:…:…). */\n    namespace: text('namespace').notNull(),\n    /** Logical source: URL / file key / slug. */\n    source: text('source').notNull(),\n    /** Coarse type for the dashboard (url/sitemap/crawl/file/text). */\n    type: text('type').notNull().default('text'),\n    title: text('title').notNull().default(''),\n    /** 'ready' | 'error' (extensible). */\n    status: text('status').notNull().default('ready'),\n    /** sha256 of the raw/cleaned source content → resync diff short-circuit. */\n    contentHash: text('content_hash').notNull(),\n    chunkCount: integer('chunk_count').notNull().default(0),\n    error: text('error'),\n    metadata: jsonb('metadata').$type<Record<string, unknown>>(),\n    createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),\n    updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),\n  },\n  (t) => [uniqueIndex('knowledge_sources_ns_source_idx').on(t.namespace, t.source)],\n);\n\nexport const knowledgeChunks = pgTable(\n  'knowledge_chunks',\n  {\n    /** `<namespace>::<source>::<chunkIndex>`. */\n    id: text('id').primaryKey(),\n    namespace: text('namespace').notNull(),\n    source: text('source').notNull(),\n    title: text('title').notNull().default(''),\n    chunkIndex: integer('chunk_index').notNull(),\n    /** The chunk text (also the basis for the generated tsvector). */\n    content: text('content').notNull(),\n    embedding: vector('embedding', EMBED_DIM).notNull(),\n    // `tsv tsvector GENERATED ALWAYS AS (to_tsvector('english', content)) STORED`\n    // is created in the SQL migration — not modelled here (Drizzle can't express\n    // a generated tsvector column). Queries reference it via raw SQL.\n    contentHash: text('content_hash').notNull(),\n    metadata: jsonb('metadata').$type<Record<string, unknown>>(),\n    createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),\n  },\n  (t) => [\n    uniqueIndex('knowledge_chunks_unique_idx').on(t.namespace, t.source, t.chunkIndex),\n    // The hot retrieval filter is WHERE namespace = ANY($1); keep it btree-indexed\n    // so the ANN scan is over a small partition, not the whole table.\n    index('knowledge_chunks_namespace_idx').on(t.namespace),\n  ],\n);\n\nexport type KnowledgeSourceRow = typeof knowledgeSources.$inferSelect;\nexport type NewKnowledgeSourceRow = typeof knowledgeSources.$inferInsert;\nexport type KnowledgeChunkRow = typeof knowledgeChunks.$inferSelect;\nexport type NewKnowledgeChunkRow = typeof knowledgeChunks.$inferInsert;\n"],"mappings":"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","names":["schema_exports","pgTable","text","timestamp","jsonb","index","text"]}