@warlock.js/ai 4.5.0 → 4.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/cjs/index.cjs +20 -1
  3. package/cjs/{src-DFibP2FQ.cjs → src-Bmajk4Qg.cjs} +1 -1
  4. package/cjs/{src-C02yzsLs.cjs → src-OZyDYHxm.cjs} +2789 -691
  5. package/cjs/src-OZyDYHxm.cjs.map +1 -0
  6. package/esm/agent/agent-config.type.d.mts +29 -0
  7. package/esm/agent/agent-config.type.d.mts.map +1 -1
  8. package/esm/agent/agent.d.mts.map +1 -1
  9. package/esm/agent/agent.mjs +126 -7
  10. package/esm/agent/agent.mjs.map +1 -1
  11. package/esm/agent/signature.mjs +57 -0
  12. package/esm/agent/signature.mjs.map +1 -0
  13. package/esm/agent/snapshot.mjs +101 -0
  14. package/esm/agent/snapshot.mjs.map +1 -0
  15. package/esm/ai-openai/src/image.mjs +5 -0
  16. package/esm/ai-openai/src/index.mjs +3 -0
  17. package/esm/ai-openai/src/sdk.mjs +3 -0
  18. package/esm/ai-openai/src/speech.mjs +5 -0
  19. package/esm/ai-openai/src/transcription.mjs +6 -0
  20. package/esm/ai-openai/src/utils/index.mjs +1 -0
  21. package/esm/ai-openai/src/utils/to-openai-messages.mjs +3 -0
  22. package/esm/ai.d.mts +45 -0
  23. package/esm/ai.d.mts.map +1 -1
  24. package/esm/ai.mjs +37 -1
  25. package/esm/ai.mjs.map +1 -1
  26. package/esm/contracts/agent/agent-options.type.d.mts +22 -2
  27. package/esm/contracts/agent/agent-options.type.d.mts.map +1 -1
  28. package/esm/contracts/agent/agent-snapshot.type.d.mts +90 -0
  29. package/esm/contracts/agent/agent-snapshot.type.d.mts.map +1 -0
  30. package/esm/contracts/agent/agent.contract.d.mts +29 -1
  31. package/esm/contracts/agent/agent.contract.d.mts.map +1 -1
  32. package/esm/contracts/agent/index.d.mts +2 -1
  33. package/esm/contracts/image-model.contract.d.mts +156 -0
  34. package/esm/contracts/image-model.contract.d.mts.map +1 -0
  35. package/esm/contracts/index.d.mts +8 -3
  36. package/esm/contracts/planner/index.d.mts +3 -2
  37. package/esm/contracts/planner/planner-config.type.d.mts +30 -0
  38. package/esm/contracts/planner/planner-config.type.d.mts.map +1 -1
  39. package/esm/contracts/planner/planner-execute-options.type.d.mts +13 -1
  40. package/esm/contracts/planner/planner-execute-options.type.d.mts.map +1 -1
  41. package/esm/contracts/planner/planner-snapshot.type.d.mts +77 -0
  42. package/esm/contracts/planner/planner-snapshot.type.d.mts.map +1 -0
  43. package/esm/contracts/planner/planner.contract.d.mts +21 -1
  44. package/esm/contracts/planner/planner.contract.d.mts.map +1 -1
  45. package/esm/contracts/result/base-report.type.d.mts +1 -1
  46. package/esm/contracts/result/base-report.type.d.mts.map +1 -1
  47. package/esm/contracts/result/base-report.type.mjs.map +1 -1
  48. package/esm/contracts/sdk-adapter.contract.d.mts +37 -0
  49. package/esm/contracts/sdk-adapter.contract.d.mts.map +1 -1
  50. package/esm/contracts/speech-model.contract.d.mts +97 -0
  51. package/esm/contracts/speech-model.contract.d.mts.map +1 -0
  52. package/esm/contracts/transcription-model.contract.d.mts +101 -0
  53. package/esm/contracts/transcription-model.contract.d.mts.map +1 -0
  54. package/esm/errors/agent-drift-error.d.mts +32 -0
  55. package/esm/errors/agent-drift-error.d.mts.map +1 -0
  56. package/esm/errors/agent-drift-error.mjs +31 -0
  57. package/esm/errors/agent-drift-error.mjs.map +1 -0
  58. package/esm/errors/error-code.type.d.mts +1 -1
  59. package/esm/errors/index.d.mts +2 -0
  60. package/esm/errors/index.mjs +2 -0
  61. package/esm/errors/planner-drift-error.d.mts +34 -0
  62. package/esm/errors/planner-drift-error.d.mts.map +1 -0
  63. package/esm/errors/planner-drift-error.mjs +33 -0
  64. package/esm/errors/planner-drift-error.mjs.map +1 -0
  65. package/esm/image/image-cost.d.mts +32 -0
  66. package/esm/image/image-cost.d.mts.map +1 -0
  67. package/esm/image/image-cost.mjs +55 -0
  68. package/esm/image/image-cost.mjs.map +1 -0
  69. package/esm/image/image.d.mts +92 -0
  70. package/esm/image/image.d.mts.map +1 -0
  71. package/esm/image/image.mjs +113 -0
  72. package/esm/image/image.mjs.map +1 -0
  73. package/esm/image/index.mjs +4 -0
  74. package/esm/index.d.mts +26 -4
  75. package/esm/index.mjs +20 -1
  76. package/esm/mock/index.d.mts +3 -0
  77. package/esm/mock/index.mjs +3 -0
  78. package/esm/mock/mock-config.type.d.mts +22 -0
  79. package/esm/mock/mock-config.type.d.mts.map +1 -1
  80. package/esm/mock/mock-image-model.d.mts +41 -0
  81. package/esm/mock/mock-image-model.d.mts.map +1 -0
  82. package/esm/mock/mock-image-model.mjs +52 -0
  83. package/esm/mock/mock-image-model.mjs.map +1 -0
  84. package/esm/mock/mock-sdk.d.mts +7 -1
  85. package/esm/mock/mock-sdk.d.mts.map +1 -1
  86. package/esm/mock/mock-sdk.mjs +27 -0
  87. package/esm/mock/mock-sdk.mjs.map +1 -1
  88. package/esm/mock/mock-speech-model.d.mts +31 -0
  89. package/esm/mock/mock-speech-model.d.mts.map +1 -0
  90. package/esm/mock/mock-speech-model.mjs +39 -0
  91. package/esm/mock/mock-speech-model.mjs.map +1 -0
  92. package/esm/mock/mock-transcription-model.d.mts +32 -0
  93. package/esm/mock/mock-transcription-model.d.mts.map +1 -0
  94. package/esm/mock/mock-transcription-model.mjs +36 -0
  95. package/esm/mock/mock-transcription-model.mjs.map +1 -0
  96. package/esm/planner/planner-run.d.mts +8 -0
  97. package/esm/planner/planner-run.d.mts.map +1 -1
  98. package/esm/planner/planner-run.mjs +161 -6
  99. package/esm/planner/planner-run.mjs.map +1 -1
  100. package/esm/planner/planner.d.mts.map +1 -1
  101. package/esm/planner/planner.mjs +25 -1
  102. package/esm/planner/planner.mjs.map +1 -1
  103. package/esm/planner/snapshot.mjs +95 -0
  104. package/esm/planner/snapshot.mjs.map +1 -0
  105. package/esm/rag/index.d.mts +7 -0
  106. package/esm/rag/index.mjs +7 -0
  107. package/esm/rag/loaders/errors.d.mts +19 -0
  108. package/esm/rag/loaders/errors.d.mts.map +1 -0
  109. package/esm/rag/loaders/errors.mjs +25 -0
  110. package/esm/rag/loaders/errors.mjs.map +1 -0
  111. package/esm/rag/loaders/index.mjs +7 -0
  112. package/esm/rag/loaders/load-html.d.mts +26 -0
  113. package/esm/rag/loaders/load-html.d.mts.map +1 -0
  114. package/esm/rag/loaders/load-html.mjs +138 -0
  115. package/esm/rag/loaders/load-html.mjs.map +1 -0
  116. package/esm/rag/loaders/load-pdf.d.mts +38 -0
  117. package/esm/rag/loaders/load-pdf.d.mts.map +1 -0
  118. package/esm/rag/loaders/load-pdf.mjs +150 -0
  119. package/esm/rag/loaders/load-pdf.mjs.map +1 -0
  120. package/esm/rag/loaders/load-text.d.mts +47 -0
  121. package/esm/rag/loaders/load-text.d.mts.map +1 -0
  122. package/esm/rag/loaders/load-text.mjs +60 -0
  123. package/esm/rag/loaders/load-text.mjs.map +1 -0
  124. package/esm/rag/loaders/load-web.d.mts +42 -0
  125. package/esm/rag/loaders/load-web.d.mts.map +1 -0
  126. package/esm/rag/loaders/load-web.mjs +89 -0
  127. package/esm/rag/loaders/load-web.mjs.map +1 -0
  128. package/esm/rag/loaders/loader.type.d.mts +89 -0
  129. package/esm/rag/loaders/loader.type.d.mts.map +1 -0
  130. package/esm/rag/store/pg-vector-store.d.mts +139 -0
  131. package/esm/rag/store/pg-vector-store.d.mts.map +1 -0
  132. package/esm/rag/store/pg-vector-store.mjs +328 -0
  133. package/esm/rag/store/pg-vector-store.mjs.map +1 -0
  134. package/esm/speech/index.mjs +3 -0
  135. package/esm/speech/speech.d.mts +65 -0
  136. package/esm/speech/speech.d.mts.map +1 -0
  137. package/esm/speech/speech.mjs +123 -0
  138. package/esm/speech/speech.mjs.map +1 -0
  139. package/esm/supervisor/entries.mjs +2 -2
  140. package/esm/supervisor/entries.mjs.map +1 -1
  141. package/esm/transcribe/audio-input.d.mts +47 -0
  142. package/esm/transcribe/audio-input.d.mts.map +1 -0
  143. package/esm/transcribe/audio-input.mjs +84 -0
  144. package/esm/transcribe/audio-input.mjs.map +1 -0
  145. package/esm/transcribe/index.mjs +4 -0
  146. package/esm/transcribe/transcribe.d.mts +64 -0
  147. package/esm/transcribe/transcribe.d.mts.map +1 -0
  148. package/esm/transcribe/transcribe.mjs +128 -0
  149. package/esm/transcribe/transcribe.mjs.map +1 -0
  150. package/llms-full.txt +753 -0
  151. package/llms.txt +5 -0
  152. package/package.json +3 -3
  153. package/skills/README.md +4 -0
  154. package/skills/durable-agent-runs/SKILL.md +135 -0
  155. package/skills/generate-images/SKILL.md +138 -0
  156. package/skills/generate-speech/SKILL.md +139 -0
  157. package/skills/rag-loaders-and-stores/SKILL.md +164 -0
  158. package/skills/transcribe-audio/SKILL.md +157 -0
  159. package/cjs/src-C02yzsLs.cjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-pdf.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/rag/loaders/load-pdf.ts"],"sourcesContent":["import type { RagDocument } from \"../contracts/rag-document.type\";\nimport { PDF_PARSE_INSTALL_INSTRUCTIONS } from \"./errors\";\nimport type { LoadPdfOptions, RagLoaderResult } from \"./loader.type\";\n\n/** Default `id` when the caller supplies none. */\nconst DEFAULT_ID = \"document\";\n\n/**\n * The slice of `pdf-parse`'s result we consume. The peer returns more\n * (`info`, `metadata`, `version`); we only need the extracted `text` and\n * page count, so we type just those to keep the dependency at arm's length.\n */\ntype PdfParseResult = {\n /** Concatenated text of every page. */\n text: string;\n /** Number of pages in the document. */\n numpages: number;\n /** Document info dictionary — `Title` lifted into metadata when present. */\n info?: { Title?: string } & Record<string, unknown>;\n};\n\n/** The `pdf-parse` module's callable default export. */\ntype PdfParseFn = (\n data: Buffer | Uint8Array,\n options?: {\n /**\n * Per-page renderer `pdf-parse` calls once per page in document order and\n * `await`s — may return the page text synchronously or as a promise.\n */\n pagerender?: (page: unknown) => string | Promise<string>;\n },\n) => Promise<PdfParseResult>;\n\n// ============================================================\n// Lazily-loaded pdf-parse (OPTIONAL peer)\n// ============================================================\n\nlet pdfParse: PdfParseFn | undefined;\nlet isModuleExists: boolean | undefined;\nlet loadingPromise: Promise<void> | undefined;\n\n/**\n * Settle the lazy import of `pdf-parse` once, concurrency-safe. A bare\n * `catch` flips the flag to `false`; the curated\n * {@link PDF_PARSE_INSTALL_INSTRUCTIONS} surfaces at first\n * {@link loadPdf} call, never a raw module-resolution stack trace. Mirrors\n * the guard moderation detector's `loadOpenAi`.\n */\nfunction loadPdfParse(): Promise<void> {\n if (isModuleExists !== undefined) {\n return Promise.resolve();\n }\n\n if (loadingPromise) {\n return loadingPromise;\n }\n\n loadingPromise = (async () => {\n try {\n // Literal specifier so `vi.mock(\"pdf-parse\")` can intercept it in tests.\n // Typed via the ambient `pdf-parse` shim in this directory, so the bare\n // import resolves even though the OPTIONAL peer is not a dependency.\n const mod = (await import(\"pdf-parse\")) as {\n default?: PdfParseFn;\n } & Partial<PdfParseFn>;\n // pdf-parse ships CommonJS — the callable is `module.exports`, surfaced\n // as `default` under ESM interop. Fall back to the namespace itself for\n // bundlers that hoist the callable to the top level.\n pdfParse = mod.default ?? (mod as unknown as PdfParseFn);\n isModuleExists = typeof pdfParse === \"function\";\n } catch {\n isModuleExists = false;\n }\n })();\n\n return loadingPromise;\n}\n\n/**\n * Coerce a {@link RagDocument}-compatible binary input into a `Buffer` for\n * `pdf-parse`. Accepts a Node `Buffer`, an `ArrayBuffer`, or a typed array\n * (`Uint8Array`) — the shapes a file read / fetch body hands back.\n */\nfunction toBuffer(input: Buffer | ArrayBuffer | Uint8Array): Buffer {\n if (Buffer.isBuffer(input)) {\n return input;\n }\n\n if (input instanceof ArrayBuffer) {\n return Buffer.from(input);\n }\n\n return Buffer.from(input.buffer, input.byteOffset, input.byteLength);\n}\n\n/**\n * Load a PDF's bytes into {@link RagDocument}(s) via the OPTIONAL `pdf-parse`\n * peer. The peer is resolved lazily on the FIRST call (not at import) so\n * importing `@warlock.js/ai` never forces it to be installed; when it is\n * absent the curated {@link PDF_PARSE_INSTALL_INSTRUCTIONS} is thrown as a\n * plain `Error` (a missing optional peer is an infrastructure fault, not a\n * content problem).\n *\n * By default the whole PDF becomes a single document carrying\n * `metadata.pageCount`. With `perPage: true`, each page becomes its own\n * document (`id` suffixed `#p<n>`, `metadata.page` set) so citations stay\n * page-precise. Document `metadata.title` comes from the PDF info\n * dictionary's `Title` (unless overridden), and `metadata.loader` is\n * `\"pdf\"`. The output is the exact shape `index()` consumes.\n *\n * @example\n * import { readFile } from \"node:fs/promises\";\n * const kb = ai.rag({ embedder, store });\n * await kb.index(await loadPdf(await readFile(\"guide.pdf\"), { id: \"guide\" }));\n *\n * @example\n * // One document per page for page-precise citations:\n * await kb.index(await loadPdf(bytes, { id: \"manual\", perPage: true }));\n *\n * @param input - The PDF bytes (`Buffer`, `ArrayBuffer`, or `Uint8Array`).\n * @param options - `perPage` plus shared `id` / `metadata` / `tags`\n * ({@link LoadPdfOptions}).\n * @returns A {@link RagLoaderResult} ready for `rag.index()`.\n * @throws {Error} carrying {@link PDF_PARSE_INSTALL_INSTRUCTIONS} when the\n * `pdf-parse` peer is not installed.\n */\nexport async function loadPdf(\n input: Buffer | ArrayBuffer | Uint8Array,\n options: LoadPdfOptions = {},\n): Promise<RagLoaderResult> {\n await loadPdfParse();\n\n if (!isModuleExists || !pdfParse) {\n throw new Error(PDF_PARSE_INSTALL_INSTRUCTIONS);\n }\n\n const id = options.id ?? DEFAULT_ID;\n const perPage = options.perPage ?? false;\n\n if (perPage) {\n return loadPerPage(input, id, options);\n }\n\n const parsed = await pdfParse(toBuffer(input));\n const text = parsed.text.trim();\n const title = parsed.info?.Title?.trim();\n\n // An image-only / empty PDF extracts no text — emit nothing so index()\n // never receives a no-op record.\n if (text.length === 0) {\n return [];\n }\n\n const doc: RagDocument = {\n id,\n text,\n metadata: {\n source: id,\n loader: \"pdf\",\n pageCount: parsed.numpages,\n ...(title ? { title } : {}),\n ...options.metadata,\n },\n tags: options.tags,\n };\n\n return [doc];\n}\n\n/** One page of a parsed PDF — the text-layer item list `pagerender` sees. */\ntype PdfPage = {\n getTextContent: (\n options?: unknown,\n ) => Promise<{ items: { str: string }[] }>;\n};\n\n/**\n * Per-page variant: render each page separately via `pdf-parse`'s\n * `pagerender` hook, accumulating one document per non-empty page. Each\n * carries `metadata.page` (1-based) and `metadata.pageCount`, and its id is\n * the base id suffixed `#p<n>` so every page-document is distinctly\n * identified for citation.\n *\n * `pdf-parse` calls `pagerender` once per page in document order and\n * `await`s the returned string, so capturing each page's joined text content\n * here gives reliable page boundaries the concatenated `text` lacks.\n */\nasync function loadPerPage(\n input: Buffer | ArrayBuffer | Uint8Array,\n id: string,\n options: LoadPdfOptions,\n): Promise<RagDocument[]> {\n const pages: string[] = [];\n\n const parsed = await pdfParse!(toBuffer(input), {\n pagerender: async (page: unknown): Promise<string> => {\n const rendered = await renderPage(page as PdfPage);\n pages.push(rendered);\n return rendered;\n },\n });\n\n const title = parsed.info?.Title?.trim();\n const docs: RagDocument[] = [];\n\n pages.forEach((pageText, index) => {\n const text = pageText.trim();\n\n if (text.length === 0) {\n return;\n }\n\n const pageNumber = index + 1;\n\n docs.push({\n id: `${id}#p${pageNumber}`,\n text,\n metadata: {\n source: id,\n loader: \"pdf\",\n page: pageNumber,\n pageCount: parsed.numpages,\n ...(title ? { title } : {}),\n ...options.metadata,\n },\n tags: options.tags,\n });\n });\n\n return docs;\n}\n\n/**\n * Join a single page's text-layer items in reading order, inserting a space\n * between items so adjacent words do not run together. Mirrors the essence\n * of `pdf-parse`'s default renderer without depending on its internals, so\n * the per-page hook stays stable across `pdf-parse` versions. A page with no\n * text layer (scanned image) renders to an empty string and is dropped.\n */\nasync function renderPage(page: PdfPage): Promise<string> {\n if (typeof page?.getTextContent !== \"function\") {\n return \"\";\n }\n\n const content = await page.getTextContent({\n normalizeWhitespace: true,\n disableCombineTextItems: false,\n });\n\n return content.items\n .map((item) => item.str)\n .join(\" \")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n"],"mappings":";;;;AAKA,MAAM,aAAa;AAgCnB,IAAI;AACJ,IAAI;AACJ,IAAI;;;;;;;;AASJ,SAAS,eAA8B;CACrC,IAAI,mBAAmB,QACrB,OAAO,QAAQ,QAAQ;CAGzB,IAAI,gBACF,OAAO;CAGT,kBAAkB,YAAY;EAC5B,IAAI;GAIF,MAAM,MAAO,MAAM,OAAO;GAM1B,WAAW,IAAI,WAAY;GAC3B,iBAAiB,OAAO,aAAa;EACvC,QAAQ;GACN,iBAAiB;EACnB;CACF,EAAC,CAAE;CAEH,OAAO;AACT;;;;;;AAOA,SAAS,SAAS,OAAkD;CAClE,IAAI,OAAO,SAAS,KAAK,GACvB,OAAO;CAGT,IAAI,iBAAiB,aACnB,OAAO,OAAO,KAAK,KAAK;CAG1B,OAAO,OAAO,KAAK,MAAM,QAAQ,MAAM,YAAY,MAAM,UAAU;AACrE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,eAAsB,QACpB,OACA,UAA0B,CAAC,GACD;CAC1B,MAAM,aAAa;CAEnB,IAAI,CAAC,kBAAkB,CAAC,UACtB,MAAM,IAAI,MAAM,8BAA8B;CAGhD,MAAM,KAAK,QAAQ,MAAM;CAGzB,IAFgB,QAAQ,WAAW,OAGjC,OAAO,YAAY,OAAO,IAAI,OAAO;CAGvC,MAAM,SAAS,MAAM,SAAS,SAAS,KAAK,CAAC;CAC7C,MAAM,OAAO,OAAO,KAAK,KAAK;CAC9B,MAAM,QAAQ,OAAO,MAAM,OAAO,KAAK;CAIvC,IAAI,KAAK,WAAW,GAClB,OAAO,CAAC;CAgBV,OAAO,CAAC;EAZN;EACA;EACA,UAAU;GACR,QAAQ;GACR,QAAQ;GACR,WAAW,OAAO;GAClB,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;GACzB,GAAG,QAAQ;EACb;EACA,MAAM,QAAQ;CAGN,CAAC;AACb;;;;;;;;;;;;AAoBA,eAAe,YACb,OACA,IACA,SACwB;CACxB,MAAM,QAAkB,CAAC;CAEzB,MAAM,SAAS,MAAM,SAAU,SAAS,KAAK,GAAG,EAC9C,YAAY,OAAO,SAAmC;EACpD,MAAM,WAAW,MAAM,WAAW,IAAe;EACjD,MAAM,KAAK,QAAQ;EACnB,OAAO;CACT,EACF,CAAC;CAED,MAAM,QAAQ,OAAO,MAAM,OAAO,KAAK;CACvC,MAAM,OAAsB,CAAC;CAE7B,MAAM,SAAS,UAAU,UAAU;EACjC,MAAM,OAAO,SAAS,KAAK;EAE3B,IAAI,KAAK,WAAW,GAClB;EAGF,MAAM,aAAa,QAAQ;EAE3B,KAAK,KAAK;GACR,IAAI,GAAG,GAAG,IAAI;GACd;GACA,UAAU;IACR,QAAQ;IACR,QAAQ;IACR,MAAM;IACN,WAAW,OAAO;IAClB,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;IACzB,GAAG,QAAQ;GACb;GACA,MAAM,QAAQ;EAChB,CAAC;CACH,CAAC;CAED,OAAO;AACT;;;;;;;;AASA,eAAe,WAAW,MAAgC;CACxD,IAAI,OAAO,MAAM,mBAAmB,YAClC,OAAO;CAQT,QAAO,MALe,KAAK,eAAe;EACxC,qBAAqB;EACrB,yBAAyB;CAC3B,CAAC,EAEa,CAAC,MACZ,KAAK,SAAS,KAAK,GAAG,CAAC,CACvB,KAAK,GAAG,CAAC,CACT,QAAQ,QAAQ,GAAG,CAAC,CACpB,KAAK;AACV"}
@@ -0,0 +1,47 @@
1
+ import { LoadTextOptions, RagLoaderResult } from "./loader.type.mjs";
2
+
3
+ //#region ../@warlock.js/ai/src/rag/loaders/load-text.d.ts
4
+ /**
5
+ * One raw text item — a bare string, or a `{ id, text, … }` record giving the
6
+ * item its own id / metadata / tags. Passing records lets a single
7
+ * {@link loadText} call turn many strings into many distinctly-identified
8
+ * {@link RagDocument}s.
9
+ */
10
+ type TextInput = string | {
11
+ /** Stable id for this item. Falls back to the option `id` + index. */id?: string; /** The text body. */
12
+ text: string; /** Per-item metadata, merged under the shared option `metadata`. */
13
+ metadata?: Record<string, unknown>; /** Per-item tags (override the shared option `tags` when present). */
14
+ tags?: string[];
15
+ };
16
+ /**
17
+ * Load plain text into {@link RagDocument}(s) — the zero-dependency base
18
+ * loader every other loader ultimately funnels into. Accepts a single
19
+ * string, a single `{ id, text }` record, or an array mixing both; each
20
+ * input becomes one document carrying `metadata.loader = "text"` plus a
21
+ * `metadata.source` (the resolved id).
22
+ *
23
+ * Caller `metadata` always wins over the loader-derived keys, and per-item
24
+ * `metadata` / `tags` (when an item is a record) layer on top of the shared
25
+ * option values. Empty / whitespace-only items are dropped — they would
26
+ * chunk to nothing anyway, so the result never carries a no-op document.
27
+ *
28
+ * The output is the exact shape `index()` consumes:
29
+ *
30
+ * @example
31
+ * const kb = ai.rag({ embedder, store });
32
+ * await kb.index(loadText("a long string of notes…"));
33
+ *
34
+ * @example
35
+ * await kb.index(loadText([
36
+ * { id: "faq-1", text: "…", metadata: { section: "billing" } },
37
+ * { id: "faq-2", text: "…" },
38
+ * ]));
39
+ *
40
+ * @param input - A string, a `{ id, text }` record, or an array of either.
41
+ * @param options - Shared `id` / `metadata` / `tags` ({@link LoadTextOptions}).
42
+ * @returns A {@link RagLoaderResult} ready to hand to `rag.index()`.
43
+ */
44
+ declare function loadText(input: TextInput | TextInput[], options?: LoadTextOptions): RagLoaderResult;
45
+ //#endregion
46
+ export { TextInput, loadText };
47
+ //# sourceMappingURL=load-text.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-text.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/rag/loaders/load-text.ts"],"mappings":";;;;;AAYA;;;;KAAY,SAAA;EAMN,sEAFA,EAAA,WAIW;EAFX,IAAA,UAII;EAFJ,QAAA,GAAW,MAAM,mBAiCP;EA/BV,IAAA;AAAA;;;;;;;;;;;;;;AAkCY;;;;;;;;;;;;;;;iBAHF,QAAA,CACd,KAAA,EAAO,SAAA,GAAY,SAAA,IACnB,OAAA,GAAS,eAAA,GACR,eAAA"}
@@ -0,0 +1,60 @@
1
+ //#region ../@warlock.js/ai/src/rag/loaders/load-text.ts
2
+ /** Default `id` when the caller supplies neither `id` nor an item id. */
3
+ const DEFAULT_ID = "document";
4
+ /**
5
+ * Load plain text into {@link RagDocument}(s) — the zero-dependency base
6
+ * loader every other loader ultimately funnels into. Accepts a single
7
+ * string, a single `{ id, text }` record, or an array mixing both; each
8
+ * input becomes one document carrying `metadata.loader = "text"` plus a
9
+ * `metadata.source` (the resolved id).
10
+ *
11
+ * Caller `metadata` always wins over the loader-derived keys, and per-item
12
+ * `metadata` / `tags` (when an item is a record) layer on top of the shared
13
+ * option values. Empty / whitespace-only items are dropped — they would
14
+ * chunk to nothing anyway, so the result never carries a no-op document.
15
+ *
16
+ * The output is the exact shape `index()` consumes:
17
+ *
18
+ * @example
19
+ * const kb = ai.rag({ embedder, store });
20
+ * await kb.index(loadText("a long string of notes…"));
21
+ *
22
+ * @example
23
+ * await kb.index(loadText([
24
+ * { id: "faq-1", text: "…", metadata: { section: "billing" } },
25
+ * { id: "faq-2", text: "…" },
26
+ * ]));
27
+ *
28
+ * @param input - A string, a `{ id, text }` record, or an array of either.
29
+ * @param options - Shared `id` / `metadata` / `tags` ({@link LoadTextOptions}).
30
+ * @returns A {@link RagLoaderResult} ready to hand to `rag.index()`.
31
+ */
32
+ function loadText(input, options = {}) {
33
+ const items = Array.isArray(input) ? input : [input];
34
+ const baseId = options.id ?? DEFAULT_ID;
35
+ const multiple = items.length > 1;
36
+ const docs = [];
37
+ items.forEach((item, index) => {
38
+ const text = typeof item === "string" ? item : item.text;
39
+ if (text.trim().length === 0) return;
40
+ const id = (typeof item === "string" ? void 0 : item.id) ?? (multiple ? `${baseId}#${index}` : baseId);
41
+ const itemMetadata = typeof item === "string" ? void 0 : item.metadata;
42
+ const itemTags = typeof item === "string" ? void 0 : item.tags;
43
+ docs.push({
44
+ id,
45
+ text,
46
+ metadata: {
47
+ source: id,
48
+ loader: "text",
49
+ ...options.metadata,
50
+ ...itemMetadata
51
+ },
52
+ tags: itemTags ?? options.tags
53
+ });
54
+ });
55
+ return docs;
56
+ }
57
+
58
+ //#endregion
59
+ export { loadText };
60
+ //# sourceMappingURL=load-text.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-text.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/rag/loaders/load-text.ts"],"sourcesContent":["import type { RagDocument } from \"../contracts/rag-document.type\";\nimport type { LoadTextOptions, RagLoaderResult } from \"./loader.type\";\n\n/** Default `id` when the caller supplies neither `id` nor an item id. */\nconst DEFAULT_ID = \"document\";\n\n/**\n * One raw text item — a bare string, or a `{ id, text, … }` record giving the\n * item its own id / metadata / tags. Passing records lets a single\n * {@link loadText} call turn many strings into many distinctly-identified\n * {@link RagDocument}s.\n */\nexport type TextInput =\n | string\n | {\n /** Stable id for this item. Falls back to the option `id` + index. */\n id?: string;\n /** The text body. */\n text: string;\n /** Per-item metadata, merged under the shared option `metadata`. */\n metadata?: Record<string, unknown>;\n /** Per-item tags (override the shared option `tags` when present). */\n tags?: string[];\n };\n\n/**\n * Load plain text into {@link RagDocument}(s) — the zero-dependency base\n * loader every other loader ultimately funnels into. Accepts a single\n * string, a single `{ id, text }` record, or an array mixing both; each\n * input becomes one document carrying `metadata.loader = \"text\"` plus a\n * `metadata.source` (the resolved id).\n *\n * Caller `metadata` always wins over the loader-derived keys, and per-item\n * `metadata` / `tags` (when an item is a record) layer on top of the shared\n * option values. Empty / whitespace-only items are dropped — they would\n * chunk to nothing anyway, so the result never carries a no-op document.\n *\n * The output is the exact shape `index()` consumes:\n *\n * @example\n * const kb = ai.rag({ embedder, store });\n * await kb.index(loadText(\"a long string of notes…\"));\n *\n * @example\n * await kb.index(loadText([\n * { id: \"faq-1\", text: \"…\", metadata: { section: \"billing\" } },\n * { id: \"faq-2\", text: \"…\" },\n * ]));\n *\n * @param input - A string, a `{ id, text }` record, or an array of either.\n * @param options - Shared `id` / `metadata` / `tags` ({@link LoadTextOptions}).\n * @returns A {@link RagLoaderResult} ready to hand to `rag.index()`.\n */\nexport function loadText(\n input: TextInput | TextInput[],\n options: LoadTextOptions = {},\n): RagLoaderResult {\n const items = Array.isArray(input) ? input : [input];\n const baseId = options.id ?? DEFAULT_ID;\n const multiple = items.length > 1;\n\n const docs: RagDocument[] = [];\n\n items.forEach((item, index) => {\n const text = typeof item === \"string\" ? item : item.text;\n\n // Drop empties up front — they chunk to nothing, so emitting them would\n // only add a no-op document for index() to skip.\n if (text.trim().length === 0) {\n return;\n }\n\n const itemId =\n typeof item === \"string\" ? undefined : item.id;\n // A single input keeps the bare base id; multiple inputs are suffixed so\n // every emitted document has a distinct, stable id.\n const id = itemId ?? (multiple ? `${baseId}#${index}` : baseId);\n\n const itemMetadata =\n typeof item === \"string\" ? undefined : item.metadata;\n const itemTags = typeof item === \"string\" ? undefined : item.tags;\n\n docs.push({\n id,\n text,\n // Loader-derived keys first, then the shared option metadata, then the\n // per-item metadata — caller intent always overrides the derived keys.\n metadata: {\n source: id,\n loader: \"text\",\n ...options.metadata,\n ...itemMetadata,\n },\n tags: itemTags ?? options.tags,\n });\n });\n\n return docs;\n}\n"],"mappings":";;AAIA,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDnB,SAAgB,SACd,OACA,UAA2B,CAAC,GACX;CACjB,MAAM,QAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;CACnD,MAAM,SAAS,QAAQ,MAAM;CAC7B,MAAM,WAAW,MAAM,SAAS;CAEhC,MAAM,OAAsB,CAAC;CAE7B,MAAM,SAAS,MAAM,UAAU;EAC7B,MAAM,OAAO,OAAO,SAAS,WAAW,OAAO,KAAK;EAIpD,IAAI,KAAK,KAAK,CAAC,CAAC,WAAW,GACzB;EAOF,MAAM,MAHJ,OAAO,SAAS,WAAW,SAAY,KAAK,QAGxB,WAAW,GAAG,OAAO,GAAG,UAAU;EAExD,MAAM,eACJ,OAAO,SAAS,WAAW,SAAY,KAAK;EAC9C,MAAM,WAAW,OAAO,SAAS,WAAW,SAAY,KAAK;EAE7D,KAAK,KAAK;GACR;GACA;GAGA,UAAU;IACR,QAAQ;IACR,QAAQ;IACR,GAAG,QAAQ;IACX,GAAG;GACL;GACA,MAAM,YAAY,QAAQ;EAC5B,CAAC;CACH,CAAC;CAED,OAAO;AACT"}
@@ -0,0 +1,42 @@
1
+ import { LoadWebOptions, RagLoaderResult } from "./loader.type.mjs";
2
+
3
+ //#region ../@warlock.js/ai/src/rag/loaders/load-web.d.ts
4
+ /**
5
+ * Fetch a URL through the SSRF-safe outbound policy and load it into a single
6
+ * {@link RagDocument} of readable text. The fetch ALWAYS goes through
7
+ * `guardedFetch` — never a raw `fetch` — so the scheme allowlist, host
8
+ * allowlist, post-DNS private-IP guard, timeout, and response-size cap from
9
+ * {@link LoadWebOptions.policy} (or the strict defaults) always apply.
10
+ *
11
+ * HTML responses are run through the same tag-strip pass as {@link loadHtml}
12
+ * (scripts/styles dropped, entities decoded, paragraph structure kept);
13
+ * non-HTML text responses (`text/plain`, markdown, …) are used verbatim.
14
+ * The document's `metadata.source` is the resolved URL, `metadata.title` is
15
+ * the page `<title>` (HTML only, unless overridden), `metadata.contentType`
16
+ * is the server-reported type, and `metadata.loader` is `"web"`.
17
+ *
18
+ * The output is the exact shape `index()` consumes, so a load feeds straight
19
+ * in:
20
+ *
21
+ * @example
22
+ * const kb = ai.rag({ embedder, store });
23
+ * await kb.index(await loadWeb("https://example.com/guide"));
24
+ *
25
+ * @example
26
+ * // Tighten the SSRF policy to a single host:
27
+ * await kb.index(await loadWeb(url, {
28
+ * policy: { hostAllowlist: ["docs.example.com"], maxBytes: 2_000_000 },
29
+ * tags: ["docs"],
30
+ * }));
31
+ *
32
+ * @param url - The absolute URL to fetch. Validated by the outbound policy.
33
+ * @param options - `policy` (the {@link OutboundPolicy}) plus shared
34
+ * `id` / `metadata` / `tags` ({@link LoadWebOptions}).
35
+ * @returns A {@link RagLoaderResult} (one document) ready for `rag.index()`.
36
+ * @throws {OutboundPolicyError} when the policy blocks the URL, the request
37
+ * times out, the body exceeds the cap, or the response is not OK.
38
+ */
39
+ declare function loadWeb(url: string, options?: LoadWebOptions): Promise<RagLoaderResult>;
40
+ //#endregion
41
+ export { loadWeb };
42
+ //# sourceMappingURL=load-web.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-web.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/rag/loaders/load-web.ts"],"mappings":";;;;;AAgEA;;;;;;;;;;;;;;AAG0B;;;;;;;;;;;;;;;;;;;iBAHJ,OAAA,CACpB,GAAA,UACA,OAAA,GAAS,cAAA,GACR,OAAA,CAAQ,eAAA"}
@@ -0,0 +1,89 @@
1
+ import { OutboundPolicyError } from "../../errors/outbound-policy-error.mjs";
2
+ import "../../errors/index.mjs";
3
+ import { guardedFetch, readTextCapped, resolveOutboundPolicy } from "../../security/outbound-policy.mjs";
4
+ import { extractTitle, htmlToText } from "./load-html.mjs";
5
+
6
+ //#region ../@warlock.js/ai/src/rag/loaders/load-web.ts
7
+ /** Browser-ish UA so servers that gate on it still return prose. */
8
+ const DEFAULT_USER_AGENT = "warlock-ai-rag-loader/1.0 (+https://github.com/warlock-js)";
9
+ /**
10
+ * Whether a `content-type` header names an HTML document (so it is run
11
+ * through the tag-strip pass) versus already-plain text (used verbatim).
12
+ */
13
+ function isHtmlContentType(contentType) {
14
+ if (!contentType) return true;
15
+ const lower = contentType.toLowerCase();
16
+ return lower.includes("text/html") || lower.includes("application/xhtml");
17
+ }
18
+ /**
19
+ * Fetch a URL through the SSRF-safe outbound policy and load it into a single
20
+ * {@link RagDocument} of readable text. The fetch ALWAYS goes through
21
+ * `guardedFetch` — never a raw `fetch` — so the scheme allowlist, host
22
+ * allowlist, post-DNS private-IP guard, timeout, and response-size cap from
23
+ * {@link LoadWebOptions.policy} (or the strict defaults) always apply.
24
+ *
25
+ * HTML responses are run through the same tag-strip pass as {@link loadHtml}
26
+ * (scripts/styles dropped, entities decoded, paragraph structure kept);
27
+ * non-HTML text responses (`text/plain`, markdown, …) are used verbatim.
28
+ * The document's `metadata.source` is the resolved URL, `metadata.title` is
29
+ * the page `<title>` (HTML only, unless overridden), `metadata.contentType`
30
+ * is the server-reported type, and `metadata.loader` is `"web"`.
31
+ *
32
+ * The output is the exact shape `index()` consumes, so a load feeds straight
33
+ * in:
34
+ *
35
+ * @example
36
+ * const kb = ai.rag({ embedder, store });
37
+ * await kb.index(await loadWeb("https://example.com/guide"));
38
+ *
39
+ * @example
40
+ * // Tighten the SSRF policy to a single host:
41
+ * await kb.index(await loadWeb(url, {
42
+ * policy: { hostAllowlist: ["docs.example.com"], maxBytes: 2_000_000 },
43
+ * tags: ["docs"],
44
+ * }));
45
+ *
46
+ * @param url - The absolute URL to fetch. Validated by the outbound policy.
47
+ * @param options - `policy` (the {@link OutboundPolicy}) plus shared
48
+ * `id` / `metadata` / `tags` ({@link LoadWebOptions}).
49
+ * @returns A {@link RagLoaderResult} (one document) ready for `rag.index()`.
50
+ * @throws {OutboundPolicyError} when the policy blocks the URL, the request
51
+ * times out, the body exceeds the cap, or the response is not OK.
52
+ */
53
+ async function loadWeb(url, options = {}) {
54
+ const policy = resolveOutboundPolicy(options.policy);
55
+ const response = await guardedFetch(url, policy, {
56
+ headers: {
57
+ "user-agent": DEFAULT_USER_AGENT,
58
+ accept: "text/html,text/*"
59
+ },
60
+ redirect: "follow"
61
+ });
62
+ if (!response.ok) throw new OutboundPolicyError(`loadWeb: fetching "${url}" returned ${response.status} ${response.statusText}`, { context: {
63
+ url,
64
+ status: response.status
65
+ } });
66
+ const contentType = response.headers.get("content-type") ?? void 0;
67
+ const raw = await readTextCapped(response, policy.maxBytes);
68
+ const isHtml = isHtmlContentType(contentType);
69
+ const text = isHtml ? htmlToText(raw) : raw.trim();
70
+ const title = isHtml ? extractTitle(raw) : void 0;
71
+ const id = options.id ?? url;
72
+ if (text.length === 0) return [];
73
+ return [{
74
+ id,
75
+ text,
76
+ metadata: {
77
+ source: url,
78
+ loader: "web",
79
+ ...title !== void 0 ? { title } : {},
80
+ ...contentType !== void 0 ? { contentType } : {},
81
+ ...options.metadata
82
+ },
83
+ tags: options.tags
84
+ }];
85
+ }
86
+
87
+ //#endregion
88
+ export { loadWeb };
89
+ //# sourceMappingURL=load-web.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-web.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/rag/loaders/load-web.ts"],"sourcesContent":["import {\n guardedFetch,\n readTextCapped,\n resolveOutboundPolicy,\n} from \"../../security/outbound-policy\";\nimport { OutboundPolicyError } from \"../../errors\";\nimport type { RagDocument } from \"../contracts/rag-document.type\";\nimport { htmlToText, extractTitle } from \"./load-html\";\nimport type { LoadWebOptions, RagLoaderResult } from \"./loader.type\";\n\n/** Browser-ish UA so servers that gate on it still return prose. */\nconst DEFAULT_USER_AGENT =\n \"warlock-ai-rag-loader/1.0 (+https://github.com/warlock-js)\";\n\n/**\n * Whether a `content-type` header names an HTML document (so it is run\n * through the tag-strip pass) versus already-plain text (used verbatim).\n */\nfunction isHtmlContentType(contentType: string | undefined): boolean {\n if (!contentType) {\n // No header — assume HTML, the common case for a fetched URL.\n return true;\n }\n\n const lower = contentType.toLowerCase();\n\n return lower.includes(\"text/html\") || lower.includes(\"application/xhtml\");\n}\n\n/**\n * Fetch a URL through the SSRF-safe outbound policy and load it into a single\n * {@link RagDocument} of readable text. The fetch ALWAYS goes through\n * `guardedFetch` — never a raw `fetch` — so the scheme allowlist, host\n * allowlist, post-DNS private-IP guard, timeout, and response-size cap from\n * {@link LoadWebOptions.policy} (or the strict defaults) always apply.\n *\n * HTML responses are run through the same tag-strip pass as {@link loadHtml}\n * (scripts/styles dropped, entities decoded, paragraph structure kept);\n * non-HTML text responses (`text/plain`, markdown, …) are used verbatim.\n * The document's `metadata.source` is the resolved URL, `metadata.title` is\n * the page `<title>` (HTML only, unless overridden), `metadata.contentType`\n * is the server-reported type, and `metadata.loader` is `\"web\"`.\n *\n * The output is the exact shape `index()` consumes, so a load feeds straight\n * in:\n *\n * @example\n * const kb = ai.rag({ embedder, store });\n * await kb.index(await loadWeb(\"https://example.com/guide\"));\n *\n * @example\n * // Tighten the SSRF policy to a single host:\n * await kb.index(await loadWeb(url, {\n * policy: { hostAllowlist: [\"docs.example.com\"], maxBytes: 2_000_000 },\n * tags: [\"docs\"],\n * }));\n *\n * @param url - The absolute URL to fetch. Validated by the outbound policy.\n * @param options - `policy` (the {@link OutboundPolicy}) plus shared\n * `id` / `metadata` / `tags` ({@link LoadWebOptions}).\n * @returns A {@link RagLoaderResult} (one document) ready for `rag.index()`.\n * @throws {OutboundPolicyError} when the policy blocks the URL, the request\n * times out, the body exceeds the cap, or the response is not OK.\n */\nexport async function loadWeb(\n url: string,\n options: LoadWebOptions = {},\n): Promise<RagLoaderResult> {\n const policy = resolveOutboundPolicy(options.policy);\n\n const response = await guardedFetch(url, policy, {\n headers: { \"user-agent\": DEFAULT_USER_AGENT, accept: \"text/html,text/*\" },\n redirect: \"follow\",\n });\n\n if (!response.ok) {\n throw new OutboundPolicyError(\n `loadWeb: fetching \"${url}\" returned ${response.status} ${response.statusText}`,\n { context: { url, status: response.status } },\n );\n }\n\n const contentType = response.headers.get(\"content-type\") ?? undefined;\n const raw = await readTextCapped(response, policy.maxBytes);\n\n const isHtml = isHtmlContentType(contentType);\n const text = isHtml ? htmlToText(raw) : raw.trim();\n const title = isHtml ? extractTitle(raw) : undefined;\n\n const id = options.id ?? url;\n\n // An empty body / all-markup page yields no document, so index() never\n // receives a no-op record.\n if (text.length === 0) {\n return [];\n }\n\n // Derived keys sit UNDER the caller's metadata so an explicit override wins.\n const doc: RagDocument = {\n id,\n text,\n metadata: {\n source: url,\n loader: \"web\",\n ...(title !== undefined ? { title } : {}),\n ...(contentType !== undefined ? { contentType } : {}),\n ...options.metadata,\n },\n tags: options.tags,\n };\n\n return [doc];\n}\n"],"mappings":";;;;;;;AAWA,MAAM,qBACJ;;;;;AAMF,SAAS,kBAAkB,aAA0C;CACnE,IAAI,CAAC,aAEH,OAAO;CAGT,MAAM,QAAQ,YAAY,YAAY;CAEtC,OAAO,MAAM,SAAS,WAAW,KAAK,MAAM,SAAS,mBAAmB;AAC1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,eAAsB,QACpB,KACA,UAA0B,CAAC,GACD;CAC1B,MAAM,SAAS,sBAAsB,QAAQ,MAAM;CAEnD,MAAM,WAAW,MAAM,aAAa,KAAK,QAAQ;EAC/C,SAAS;GAAE,cAAc;GAAoB,QAAQ;EAAmB;EACxE,UAAU;CACZ,CAAC;CAED,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,oBACR,sBAAsB,IAAI,aAAa,SAAS,OAAO,GAAG,SAAS,cACnE,EAAE,SAAS;EAAE;EAAK,QAAQ,SAAS;CAAO,EAAE,CAC9C;CAGF,MAAM,cAAc,SAAS,QAAQ,IAAI,cAAc,KAAK;CAC5D,MAAM,MAAM,MAAM,eAAe,UAAU,OAAO,QAAQ;CAE1D,MAAM,SAAS,kBAAkB,WAAW;CAC5C,MAAM,OAAO,SAAS,WAAW,GAAG,IAAI,IAAI,KAAK;CACjD,MAAM,QAAQ,SAAS,aAAa,GAAG,IAAI;CAE3C,MAAM,KAAK,QAAQ,MAAM;CAIzB,IAAI,KAAK,WAAW,GAClB,OAAO,CAAC;CAiBV,OAAO,CAAC;EAZN;EACA;EACA,UAAU;GACR,QAAQ;GACR,QAAQ;GACR,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;GACvC,GAAI,gBAAgB,SAAY,EAAE,YAAY,IAAI,CAAC;GACnD,GAAG,QAAQ;EACb;EACA,MAAM,QAAQ;CAGN,CAAC;AACb"}
@@ -0,0 +1,89 @@
1
+ import { OutboundPolicy } from "../../security/outbound-policy.type.mjs";
2
+ import { RagDocument } from "../contracts/rag-document.type.mjs";
3
+
4
+ //#region ../@warlock.js/ai/src/rag/loaders/loader.type.d.ts
5
+ /**
6
+ * The shape every loader emits — the **exact** {@link RagDocument} (or array
7
+ * of them) that `ai.rag(config).index()` consumes, so a load result is fed
8
+ * straight in with no adapter:
9
+ *
10
+ * @example
11
+ * const kb = ai.rag({ embedder, store });
12
+ * await kb.index(await ai.rag.loadWeb("https://example.com/guide"));
13
+ *
14
+ * A loader may emit one document (the common case for a single file / page)
15
+ * or several (e.g. one document per PDF page), so the result is uniformly an
16
+ * **array** — `index()` accepts an array, so callers never branch on arity.
17
+ */
18
+ type RagLoaderResult = RagDocument[];
19
+ /**
20
+ * Metadata keys loaders attach to every {@link RagDocument} they emit, on
21
+ * top of any caller-supplied `metadata`. Each is optional and only present
22
+ * when the loader could determine it. These keys round-trip through
23
+ * chunking onto the final citation, so a retrieved chunk can be traced back
24
+ * to its `source` URL / `title` / `page`.
25
+ */
26
+ type RagLoaderMetadata = {
27
+ /**
28
+ * Where the document came from — a URL (web loader), a logical name, or
29
+ * the caller-supplied `id`. Always a string when present.
30
+ */
31
+ source?: string; /** Human-readable title, e.g. an HTML `<title>` or PDF document title. */
32
+ title?: string; /** Discriminator for the loader that produced the document. */
33
+ loader?: RagLoaderType; /** 1-based page number — set by {@link loadPdf} on per-page documents. */
34
+ page?: number; /** Total page count of the source — set by {@link loadPdf}. */
35
+ pageCount?: number; /** MIME content-type reported by the server — set by the web loader. */
36
+ contentType?: string;
37
+ };
38
+ /** Discriminator identifying which loader produced a {@link RagDocument}. */
39
+ type RagLoaderType = "text" | "html" | "web" | "pdf";
40
+ /**
41
+ * Shared options every loader accepts. The `id` and `metadata` flow onto
42
+ * the emitted {@link RagDocument} verbatim (loader-derived metadata is
43
+ * merged UNDER the caller's, so an explicit `metadata.title` always wins),
44
+ * and `tags` propagate to every chunk for `retrieve({ tags })` filtering.
45
+ */
46
+ type RagLoaderOptions = {
47
+ /**
48
+ * Stable source id for the emitted document(s). Falls back to a
49
+ * loader-specific default (the URL for the web loader, `"document"`
50
+ * otherwise). Multi-document loaders suffix this (e.g. `"<id>#p3"`).
51
+ */
52
+ id?: string;
53
+ /**
54
+ * Extra metadata merged onto every emitted document. Caller keys take
55
+ * precedence over the loader's derived keys (`source`, `title`, …).
56
+ */
57
+ metadata?: Record<string, unknown>; /** Tags applied to every chunk written from the emitted document(s). */
58
+ tags?: string[];
59
+ };
60
+ /** Options for the HTML loader — {@link RagLoaderOptions} only. */
61
+ type LoadHtmlOptions = RagLoaderOptions;
62
+ /** Options for the plain-text loader — {@link RagLoaderOptions} only. */
63
+ type LoadTextOptions = RagLoaderOptions;
64
+ /** Options for the PDF loader. */
65
+ type LoadPdfOptions = RagLoaderOptions & {
66
+ /**
67
+ * Emit one {@link RagDocument} per page (each tagged with `page` /
68
+ * `pageCount` metadata) instead of a single concatenated document.
69
+ * Per-page documents keep citations page-precise. Default `false`.
70
+ */
71
+ perPage?: boolean;
72
+ };
73
+ /**
74
+ * Options for the web loader. Extends {@link RagLoaderOptions} with an
75
+ * {@link OutboundPolicy} — the SSRF-safe fetch policy the loader hands to
76
+ * `guardedFetch`. Omit it and the strict defaults apply (https-only,
77
+ * private-IP deny on, 10s timeout, 5 MiB cap).
78
+ */
79
+ type LoadWebOptions = RagLoaderOptions & {
80
+ /**
81
+ * The {@link OutboundPolicy} governing the fetch. The web loader NEVER
82
+ * issues a raw `fetch` — every request goes through the policy's
83
+ * `guardedFetch`, so an SSRF / oversized-body guard always applies.
84
+ */
85
+ policy?: OutboundPolicy;
86
+ };
87
+ //#endregion
88
+ export { LoadHtmlOptions, LoadPdfOptions, LoadTextOptions, LoadWebOptions, RagLoaderMetadata, RagLoaderOptions, RagLoaderResult, RagLoaderType };
89
+ //# sourceMappingURL=loader.type.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.type.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/rag/loaders/loader.type.ts"],"mappings":";;;;;;AAgBA;;;;AAAyC;AASzC;;;;;;KATY,eAAA,GAAkB,WAAW;;;;;;AAwB5B;AAIb;KAnBY,iBAAA;;;AAmBa;AAQzB;EAtBE,MAAA;EAEA,KAAA,WA0BA;EAxBA,MAAA,GAAS,aAAa,EA6BX;EA3BX,IAAA,WA6BI;EA3BJ,SAAA,WA+BU;EA7BV,WAAA;AAAA;;KAIU,aAAA;AA4BZ;;;;AAA8C;AAG9C;AAHA,KApBY,gBAAA;;;AA6BH;AAST;;EAhCE,EAAA;EAsCuB;;;;EAjCvB,QAAA,GAAW,MAAM,mBAiCM;EA/BvB,IAAA;AAAA;;KAIU,eAAA,GAAkB,gBAAgB;;KAGlC,eAAA,GAAkB,gBAAgB;;KAGlC,cAAA,GAAiB,gBAAgB;;;;;;EAM3C,OAAO;AAAA;;;;;;;KASG,cAAA,GAAiB,gBAAA;;;;;;EAM3B,MAAA,GAAS,cAAc;AAAA"}
@@ -0,0 +1,139 @@
1
+ import { VectorStore } from "./vector-store.contract.mjs";
2
+
3
+ //#region ../@warlock.js/ai/src/rag/store/pg-vector-store.d.ts
4
+ /**
5
+ * Minimal `pg`-compatible client surface the Postgres {@link VectorStore}
6
+ * depends on. Both `pg.Pool` and `pg.Client` satisfy it — the store only
7
+ * ever calls `query`.
8
+ *
9
+ * `@warlock.js/ai` takes **no** hard dependency on `pg`; the dev installs
10
+ * it (an optional peer) and passes the client in. Structurally identical
11
+ * to the snapshot / human-interrupt stores' `PgClientLike`, so a single
12
+ * pool can back the orchestrator checkpoint/snapshot tables, the
13
+ * interrupt table, and this vectors table alike.
14
+ */
15
+ interface PgClientLike {
16
+ query(text: string, params?: unknown[]): Promise<{
17
+ rows: unknown[];
18
+ }>;
19
+ }
20
+ /**
21
+ * Options for the Postgres {@link VectorStore}.
22
+ *
23
+ * Two mutually-supportive ways to supply the connection (mirroring
24
+ * `ai.human.interrupt.pg`):
25
+ * - **`client`** — pass an already-built `pg.Pool` / `pg.Client` (anything
26
+ * satisfying {@link PgClientLike}). The store only ever calls `query`
27
+ * and never opens or closes it; one pool can back several stores.
28
+ * - **`connectionString`** — let the store lazily `import("pg")` and build
29
+ * its own `Pool`. `@warlock.js/ai` takes **no** hard dependency on
30
+ * `pg` (an optional peer); when it is absent the store throws a curated
31
+ * install string at first use, never a raw module-resolution stack trace
32
+ * at import.
33
+ *
34
+ * Exactly one of the two must be present.
35
+ */
36
+ interface PgVectorStoreOptions {
37
+ /** An already-built `pg.Pool` / `pg.Client` — anything matching {@link PgClientLike}. */
38
+ client?: PgClientLike;
39
+ /** Connection string the store passes to a lazily-imported `pg.Pool`. */
40
+ connectionString?: string;
41
+ /**
42
+ * Backing table name. Defaults to `warlock_ai_rag_vectors`. Must be a
43
+ * safe SQL identifier — it is interpolated into DDL/DML.
44
+ */
45
+ table?: string;
46
+ /**
47
+ * Embedding dimensionality used in the `CREATE TABLE` DDL emitted by
48
+ * {@link VectorStore.schema | ensureSchema}. Defaults to `1536`
49
+ * (OpenAI `text-embedding-3-small`). The column is declared
50
+ * `vector(N)`; queries and upserts never re-state it, so an existing
51
+ * table provisioned at a different size is unaffected — only the DDL
52
+ * helper reads this.
53
+ */
54
+ dimensions?: number;
55
+ /**
56
+ * Approximate-nearest-neighbour index strategy baked into the DDL
57
+ * emitted by {@link VectorStore.schema | ensureSchema}. Defaults to
58
+ * `"hnsw"` (better recall/latency on modern pgvector). Use `"ivfflat"`
59
+ * for the classic list-partitioned index, or `"none"` to emit no ANN
60
+ * index (exact scan — correct, but linear in row count).
61
+ */
62
+ index?: "hnsw" | "ivfflat" | "none";
63
+ /**
64
+ * `lists` parameter for an `ivfflat` index (ignored for `hnsw` / `none`).
65
+ * Defaults to `100`. Tune toward `rows / 1000` for large tables.
66
+ */
67
+ ivfflatLists?: number;
68
+ }
69
+ /**
70
+ * Serialize a JS `number[]` to the pgvector text literal: `[1,2,3]`.
71
+ * pgvector accepts a vector either as this bracketed literal or via a
72
+ * typed parameter; passing the literal string + an explicit `::vector`
73
+ * cast keeps the store driver-agnostic (no dependency on a registered
74
+ * `pg` type parser).
75
+ *
76
+ * Non-finite components (`NaN` / `±Infinity`) are rejected — pgvector
77
+ * stores only finite floats, and silently coercing them would corrupt the
78
+ * index. The check is cheap relative to the embed call that produced the
79
+ * vector.
80
+ *
81
+ * @example
82
+ * vectorLiteral([1, 0.5, -2]); // "[1,0.5,-2]"
83
+ */
84
+ declare function vectorLiteral(vector: number[]): string;
85
+ /**
86
+ * The {@link VectorStore} surface plus the pg store's extra DDL helpers.
87
+ * `schema()` / `ensureSchema()` are not part of the base contract (the
88
+ * cache store has no backing table), so the factory's return type widens
89
+ * it for callers that want the migration SQL.
90
+ */
91
+ interface PgVectorStoreInstance extends VectorStore {
92
+ /** Reference migration DDL (extension + table + indexes). Never executed. */
93
+ schema(): string;
94
+ /** Alias for {@link PgVectorStoreInstance.schema} — reads better in a migration script. */
95
+ ensureSchema(): string;
96
+ }
97
+ /**
98
+ * Create a Postgres + pgvector-backed {@link VectorStore} for the RAG
99
+ * pipeline. Either pass a live `pg.Pool` / `pg.Client` (`{ client }`) —
100
+ * `@warlock.js/ai` never imports `pg` in that case — or a
101
+ * `{ connectionString }` and let the store lazily `import("pg")` to build
102
+ * its own pool. When `pg` is not installed, a curated install string
103
+ * surfaces on first use, never at import.
104
+ *
105
+ * Run {@link PgVectorStoreInstance.ensureSchema} through your migration
106
+ * tool once before use (it enables the `vector` extension, creates the
107
+ * table, and builds the tag + ANN indexes); the store never auto-migrates.
108
+ *
109
+ * Index and query MUST use the same embedding model — the `vector(N)`
110
+ * column width is fixed at table-creation time from `dimensions`.
111
+ *
112
+ * @example
113
+ * import { Pool } from "pg";
114
+ * import { ai } from "@warlock.js/ai";
115
+ *
116
+ * const pool = new Pool({ connectionString: process.env.DATABASE_URL });
117
+ * const store = ai.rag.pgVectorStore({ client: pool, dimensions: 1536 });
118
+ *
119
+ * // Once, via your migration tooling:
120
+ * // await pool.query(store.ensureSchema());
121
+ *
122
+ * const kb = ai.rag({
123
+ * name: "docs",
124
+ * embedder: openai.embedder({ name: "text-embedding-3-small" }),
125
+ * store,
126
+ * });
127
+ *
128
+ * @example
129
+ * // Let the store build its own pool from a connection string:
130
+ * const store = ai.rag.pgVectorStore({
131
+ * connectionString: process.env.DATABASE_URL,
132
+ * index: "ivfflat",
133
+ * ivfflatLists: 200,
134
+ * });
135
+ */
136
+ declare function pgVectorStore(options: PgVectorStoreOptions): PgVectorStoreInstance;
137
+ //#endregion
138
+ export { PgClientLike, PgVectorStoreInstance, PgVectorStoreOptions, pgVectorStore, vectorLiteral };
139
+ //# sourceMappingURL=pg-vector-store.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pg-vector-store.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/rag/store/pg-vector-store.ts"],"mappings":";;;;;AAaA;;;;;;;;;UAAiB,YAAA;EACf,KAAA,CAAM,IAAA,UAAc,MAAA,eAAqB,OAAO;IAAG,IAAA;EAAA;AAAA;;;;;;;;;;AAuDvC;AA4Fd;;;;AAA8C;AA6U9C;UA7ciB,oBAAA;;EAEf,MAAA,GAAS,YAAY;EA2cwB;EAxc7C,gBAAA;EA4cA;;AAAY;AA0Cd;EAhfE,KAAA;;;;;;;AAgfiF;;EAtejF,UAAA;;;;;;;;EASA,KAAA;;;;;EAMA,YAAA;AAAA;;;;;;;;;;;;;;;;iBA4Fc,aAAA,CAAc,MAAgB;;;;;;;UA6U7B,qBAAA,SAA8B,WAAW;;EAExD,MAAA;;EAEA,YAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0Cc,aAAA,CAAc,OAAA,EAAS,oBAAA,GAAuB,qBAAqB"}