@ontos-ai/knowhere-sdk 2.0.0 → 2.1.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.
package/dist/index.mjs CHANGED
@@ -309,8 +309,8 @@ function enrichParseResult(parseResult2, scope) {
309
309
  }
310
310
  return parseResult2;
311
311
  }
312
- function sanitizePath(path3) {
313
- let sanitized = path3.replace(/^\/+/, "");
312
+ function sanitizePath(path4) {
313
+ let sanitized = path4.replace(/^\/+/, "");
314
314
  sanitized = sanitized.replace(/\.\.(\/|\\)/g, "");
315
315
  sanitized = sanitized.replace(/\\/g, "/");
316
316
  return sanitized;
@@ -696,8 +696,8 @@ var BaseResource = class {
696
696
  constructor(httpClient) {
697
697
  this.httpClient = httpClient;
698
698
  }
699
- endpoint(path3) {
700
- return `/v2${path3.startsWith("/") ? path3 : `/${path3}`}`;
699
+ endpoint(path4) {
700
+ return `/v2${path4.startsWith("/") ? path4 : `/${path4}`}`;
701
701
  }
702
702
  };
703
703
 
@@ -913,17 +913,28 @@ async function saveExpandedParseResult(result, directory) {
913
913
  if (result.rawZip.length > 0) {
914
914
  const didExtractZip = await tryExtractRawZip(result.rawZip, directory);
915
915
  if (didExtractZip) {
916
+ await writeExpandedParseResultMetadata(result, directory);
916
917
  return directory;
917
918
  }
918
919
  }
919
920
  await fs2.mkdir(directory, { recursive: true });
921
+ await writeExpandedParseResultMetadata(result, directory);
922
+ for (const imageChunk of result.imageChunks) {
923
+ await writeBinaryAsset(directory, imageChunk.filePath, imageChunk.data);
924
+ }
925
+ for (const tableChunk of result.tableChunks) {
926
+ await writeTextAsset(directory, tableChunk.filePath, tableChunk.html);
927
+ }
928
+ return directory;
929
+ }
930
+ async function writeExpandedParseResultMetadata(result, directory) {
920
931
  await fs2.writeFile(join(directory, "manifest.json"), JSON.stringify(result.manifest, null, 2));
921
932
  if (result.docNav) {
922
933
  await fs2.writeFile(join(directory, "doc_nav.json"), JSON.stringify(result.docNav, null, 2));
923
934
  }
924
935
  await fs2.writeFile(
925
936
  join(directory, "chunks.json"),
926
- JSON.stringify(serializeChunks(result.chunks), null, 2)
937
+ JSON.stringify(keysToSnake(serializeChunks(result.chunks)), null, 2)
927
938
  );
928
939
  if (result.chunksSlim) {
929
940
  await fs2.writeFile(
@@ -952,13 +963,6 @@ async function saveExpandedParseResult(result, directory) {
952
963
  if (result.hierarchyViewHtml) {
953
964
  await fs2.writeFile(join(directory, "hierarchy_view.html"), result.hierarchyViewHtml);
954
965
  }
955
- for (const imageChunk of result.imageChunks) {
956
- await writeBinaryAsset(directory, imageChunk.filePath, imageChunk.data);
957
- }
958
- for (const tableChunk of result.tableChunks) {
959
- await writeTextAsset(directory, tableChunk.filePath, tableChunk.html);
960
- }
961
- return directory;
962
966
  }
963
967
  async function tryExtractRawZip(zipBuffer, directory) {
964
968
  try {
@@ -1097,13 +1101,14 @@ function buildTextChunk(chunkData) {
1097
1101
  };
1098
1102
  }
1099
1103
  function buildPageChunk(chunkData) {
1104
+ const metadata = chunkData.metadata ?? {};
1100
1105
  return {
1101
1106
  chunkId: chunkData.chunkId ?? "",
1102
1107
  type: "page",
1103
1108
  contentSource: chunkData.contentSource ?? "summary",
1104
1109
  content: chunkData.content ?? "",
1105
1110
  path: chunkData.path ?? "",
1106
- metadata: chunkData.metadata ?? {}
1111
+ metadata
1107
1112
  };
1108
1113
  }
1109
1114
  function buildImageChunk(chunkData, filePath, imageBuffer) {
@@ -1114,6 +1119,7 @@ function buildImageChunk(chunkData, filePath, imageBuffer) {
1114
1119
  content: chunkData.content ?? "",
1115
1120
  path: chunkData.path ?? "",
1116
1121
  filePath,
1122
+ assetUrl: chunkData.assetUrl,
1117
1123
  data: imageBuffer,
1118
1124
  metadata: chunkData.metadata ?? {},
1119
1125
  get format() {
@@ -1132,6 +1138,7 @@ function buildTableChunk(chunkData, filePath, html) {
1132
1138
  content: chunkData.content ?? "",
1133
1139
  path: chunkData.path ?? "",
1134
1140
  filePath,
1141
+ assetUrl: chunkData.assetUrl,
1135
1142
  html,
1136
1143
  metadata: chunkData.metadata ?? {},
1137
1144
  async save(directory) {
@@ -1226,6 +1233,7 @@ function serializeChunks(chunks) {
1226
1233
  };
1227
1234
  if (chunk.type === "image" || chunk.type === "table") {
1228
1235
  rawChunk.filePath = chunk.filePath;
1236
+ rawChunk.assetUrl = chunk.assetUrl;
1229
1237
  }
1230
1238
  return rawChunk;
1231
1239
  })
@@ -1434,6 +1442,14 @@ var Documents = class extends BaseResource {
1434
1442
  this.createChunkGetRequestConfig(params)
1435
1443
  );
1436
1444
  }
1445
+ /**
1446
+ * Get a signed normalized source-file URL for compatibility and debugging.
1447
+ */
1448
+ async getPageCitationSource(documentId) {
1449
+ return this.httpClient.get(
1450
+ this.endpoint(`/documents/${documentId}/files/page-citation-source`)
1451
+ );
1452
+ }
1437
1453
  /**
1438
1454
  * Archive one canonical document by ID.
1439
1455
  */
@@ -1506,6 +1522,514 @@ var Documents = class extends BaseResource {
1506
1522
  // src/knowledge/knowledge.ts
1507
1523
  import { createHash as createHash2 } from "crypto";
1508
1524
 
1525
+ // src/storage/asset-storage.ts
1526
+ import JSZip2 from "jszip";
1527
+ var pageCitationAssetsMetadataKey = "pageAssets";
1528
+ var defaultImageContentType = "application/octet-stream";
1529
+ var tableContentType = "text/html; charset=utf-8";
1530
+ var snapshotManifestKey = "manifest/current.json";
1531
+ var snapshotIndexKey = "index.json";
1532
+ var defaultSnapshotChunkPageSize = 200;
1533
+ var snapshotJsonContentType = "application/json; charset=utf-8";
1534
+ async function storeParseResultAssets(result, options) {
1535
+ if (!options) {
1536
+ return {
1537
+ result,
1538
+ assetUrlsByFilePath: {}
1539
+ };
1540
+ }
1541
+ const normalizedKeyPrefix = normalizeStorageKeyPrefix(options.keyPrefix);
1542
+ const assets = dedupeStorageAssets(await collectStorageAssets(result, normalizedKeyPrefix));
1543
+ const assetUrlsByFilePath = {};
1544
+ for (const asset of assets) {
1545
+ const storedUrl = await storeAsset(asset, options.adapter, options.skipExisting ?? true);
1546
+ if (storedUrl) {
1547
+ assetUrlsByFilePath[asset.sourcePath] = storedUrl;
1548
+ }
1549
+ }
1550
+ const normalizedResult = Object.keys(assetUrlsByFilePath).length === 0 ? result : rewriteResultAssetUrls(result, assetUrlsByFilePath);
1551
+ const snapshot = await storeParsedSnapshot({
1552
+ result: normalizedResult,
1553
+ assetUrlsByFilePath,
1554
+ keyPrefix: normalizedKeyPrefix,
1555
+ options
1556
+ });
1557
+ return {
1558
+ result: normalizedResult,
1559
+ assetUrlsByFilePath,
1560
+ snapshot
1561
+ };
1562
+ }
1563
+ async function storeParsedSnapshot(input) {
1564
+ const pageSize = normalizeSnapshotChunkPageSize(input.options.chunkPageSize);
1565
+ const totalChunks = input.result.chunks.length;
1566
+ const totalPages = Math.max(1, Math.ceil(totalChunks / pageSize));
1567
+ const chunkPageReferences = [];
1568
+ const chunkPageUrlsByPage = {};
1569
+ for (let page = 1; page <= totalPages; page += 1) {
1570
+ const pageChunks = input.result.chunks.slice((page - 1) * pageSize, page * pageSize);
1571
+ const pageKey = `${input.keyPrefix}/chunks/page-${page}.json`;
1572
+ const chunkPage = {
1573
+ version: 1,
1574
+ jobId: input.result.jobId,
1575
+ revisionKey: input.options.revisionKey ?? input.result.jobId,
1576
+ documentId: input.result.documentId,
1577
+ namespace: input.result.namespace,
1578
+ sourceFileName: input.result.manifest.sourceFileName,
1579
+ page,
1580
+ pageSize,
1581
+ total: totalChunks,
1582
+ totalPages,
1583
+ chunks: pageChunks.map(
1584
+ (chunk, index) => toSnapshotChunk(chunk, (page - 1) * pageSize + index + 1)
1585
+ )
1586
+ };
1587
+ const pageUrl = await writeJsonObject({
1588
+ key: pageKey,
1589
+ value: chunkPage,
1590
+ adapter: input.options.adapter
1591
+ });
1592
+ if (pageUrl) chunkPageUrlsByPage[page] = pageUrl;
1593
+ chunkPageReferences.push({
1594
+ page,
1595
+ pageSize,
1596
+ chunkCount: pageChunks.length,
1597
+ key: pageKey,
1598
+ url: pageUrl
1599
+ });
1600
+ }
1601
+ const manifest = {
1602
+ version: 1,
1603
+ kind: "knowhere-parsed-result-snapshot",
1604
+ jobId: input.result.jobId,
1605
+ revisionKey: input.options.revisionKey ?? input.result.jobId,
1606
+ documentId: input.result.documentId,
1607
+ namespace: input.result.namespace,
1608
+ sourceFileName: input.result.manifest.sourceFileName,
1609
+ totalChunks,
1610
+ typeCounts: countSnapshotChunkTypes(input.result.chunks),
1611
+ chunkPageSize: pageSize,
1612
+ chunkPages: chunkPageReferences,
1613
+ assetUrlsByFilePath: input.assetUrlsByFilePath,
1614
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1615
+ };
1616
+ const manifestKey = `${input.keyPrefix}/${snapshotManifestKey}`;
1617
+ const indexKey = `${input.keyPrefix}/${snapshotIndexKey}`;
1618
+ const manifestUrl = await writeJsonObject({
1619
+ key: manifestKey,
1620
+ value: manifest,
1621
+ adapter: input.options.adapter
1622
+ });
1623
+ const indexUrl = await writeJsonObject({
1624
+ key: indexKey,
1625
+ value: manifest,
1626
+ adapter: input.options.adapter
1627
+ });
1628
+ return {
1629
+ manifest,
1630
+ manifestKey,
1631
+ manifestUrl,
1632
+ indexKey,
1633
+ indexUrl,
1634
+ chunkPageUrlsByPage
1635
+ };
1636
+ }
1637
+ function countSnapshotChunkTypes(chunks) {
1638
+ return chunks.reduce(
1639
+ (counts, chunk) => {
1640
+ counts[chunk.type] += 1;
1641
+ return counts;
1642
+ },
1643
+ { text: 0, image: 0, table: 0, page: 0 }
1644
+ );
1645
+ }
1646
+ async function writeJsonObject(input) {
1647
+ const result = await input.adapter.writeObject({
1648
+ key: input.key,
1649
+ body: Buffer.from(JSON.stringify(input.value, null, 2), "utf8"),
1650
+ contentType: snapshotJsonContentType,
1651
+ metadata: {
1652
+ kind: "knowhere-parsed-result-snapshot"
1653
+ }
1654
+ });
1655
+ return result.url ?? await input.adapter.getObjectUrl?.(result.key) ?? void 0;
1656
+ }
1657
+ function normalizeSnapshotChunkPageSize(value) {
1658
+ if (!Number.isInteger(value) || value === void 0) return defaultSnapshotChunkPageSize;
1659
+ return Math.min(Math.max(value, 1), 1e3);
1660
+ }
1661
+ function toSnapshotChunk(chunk, position) {
1662
+ const filePath = getSnapshotChunkFilePath(chunk);
1663
+ return {
1664
+ id: chunk.chunkId,
1665
+ chunkId: chunk.chunkId,
1666
+ chunkType: chunk.type,
1667
+ contentSource: chunk.contentSource,
1668
+ content: chunk.content,
1669
+ sectionPath: normalizeSnapshotSectionPath(chunk.path),
1670
+ sourceChunkPath: chunk.path,
1671
+ filePath,
1672
+ sortOrder: position,
1673
+ metadata: cloneChunkMetadata(chunk.metadata),
1674
+ assetUrl: getSnapshotChunkAssetUrl(chunk)
1675
+ };
1676
+ }
1677
+ function normalizeSnapshotSectionPath(path4) {
1678
+ return path4.length > 0 ? path4 : void 0;
1679
+ }
1680
+ function getSnapshotChunkFilePath(chunk) {
1681
+ if (chunk.type === "image" || chunk.type === "table") {
1682
+ return chunk.filePath;
1683
+ }
1684
+ const filePath = chunk.metadata.filePath;
1685
+ return typeof filePath === "string" ? filePath : void 0;
1686
+ }
1687
+ function getSnapshotChunkAssetUrl(chunk) {
1688
+ return chunk.type === "image" || chunk.type === "table" ? chunk.assetUrl : void 0;
1689
+ }
1690
+ function cloneChunkMetadata(metadata) {
1691
+ return { ...metadata };
1692
+ }
1693
+ async function collectStorageAssets(result, keyPrefix) {
1694
+ const resultZip = await loadResultZipForPageCitationAssets(result);
1695
+ return [
1696
+ ...result.imageChunks.flatMap((chunk) => collectImageStorageAsset(chunk, keyPrefix)),
1697
+ ...result.tableChunks.flatMap((chunk) => collectTableStorageAsset(chunk, keyPrefix)),
1698
+ ...resultZip ? await collectPageCitationStorageAssets(result.pageChunks, keyPrefix, resultZip) : []
1699
+ ];
1700
+ }
1701
+ function collectImageStorageAsset(chunk, keyPrefix) {
1702
+ const sourcePath = normalizeResultAssetPath(chunk.filePath);
1703
+ if (!sourcePath) return [];
1704
+ return [
1705
+ {
1706
+ sourcePath,
1707
+ storageKey: `${keyPrefix}/${sourcePath}`,
1708
+ body: chunk.data,
1709
+ contentType: getImageContentType(chunk.filePath),
1710
+ metadata: {
1711
+ chunkId: chunk.chunkId,
1712
+ chunkType: chunk.type,
1713
+ sourcePath
1714
+ }
1715
+ }
1716
+ ];
1717
+ }
1718
+ function collectTableStorageAsset(chunk, keyPrefix) {
1719
+ const sourcePath = normalizeResultAssetPath(chunk.filePath);
1720
+ if (!sourcePath) return [];
1721
+ return [
1722
+ {
1723
+ sourcePath,
1724
+ storageKey: `${keyPrefix}/${sourcePath}`,
1725
+ body: Buffer.from(chunk.html, "utf8"),
1726
+ contentType: tableContentType,
1727
+ metadata: {
1728
+ chunkId: chunk.chunkId,
1729
+ chunkType: chunk.type,
1730
+ sourcePath
1731
+ }
1732
+ }
1733
+ ];
1734
+ }
1735
+ async function collectPageCitationStorageAssets(chunks, keyPrefix, resultZip) {
1736
+ const assets = [];
1737
+ for (const chunk of chunks) {
1738
+ const pageAssets = parsePageCitationAssets(chunk.metadata[pageCitationAssetsMetadataKey]);
1739
+ for (const pageAsset of pageAssets) {
1740
+ const sourcePath = normalizeResultAssetPath(pageAsset.artifactRef);
1741
+ if (!sourcePath) continue;
1742
+ const zipEntry = resultZip.file(sourcePath);
1743
+ if (!zipEntry) continue;
1744
+ assets.push({
1745
+ sourcePath,
1746
+ storageKey: `${keyPrefix}/${sourcePath}`,
1747
+ body: await zipEntry.async("uint8array"),
1748
+ contentType: pageAsset.contentType,
1749
+ metadata: getPageCitationAssetMetadata(chunk, pageAsset, sourcePath)
1750
+ });
1751
+ }
1752
+ }
1753
+ return assets;
1754
+ }
1755
+ function getPageCitationAssetMetadata(chunk, pageAsset, sourcePath) {
1756
+ return {
1757
+ chunkId: chunk.chunkId,
1758
+ chunkType: chunk.type,
1759
+ pageNum: String(pageAsset.pageNum),
1760
+ sourcePath
1761
+ };
1762
+ }
1763
+ async function loadResultZipForPageCitationAssets(result) {
1764
+ if (!hasPageCitationAssetReferences(result) || result.rawZip.length === 0) {
1765
+ return null;
1766
+ }
1767
+ try {
1768
+ return await JSZip2.loadAsync(result.rawZip);
1769
+ } catch (cause) {
1770
+ throw new Error("Unable to read page citation assets from the Knowhere result ZIP.", {
1771
+ cause
1772
+ });
1773
+ }
1774
+ }
1775
+ function hasPageCitationAssetReferences(result) {
1776
+ return result.pageChunks.some(
1777
+ (chunk) => parsePageCitationAssets(chunk.metadata[pageCitationAssetsMetadataKey]).length > 0
1778
+ );
1779
+ }
1780
+ function dedupeStorageAssets(assets) {
1781
+ const assetsBySourcePath = /* @__PURE__ */ new Map();
1782
+ for (const asset of assets) {
1783
+ if (!assetsBySourcePath.has(asset.sourcePath)) {
1784
+ assetsBySourcePath.set(asset.sourcePath, asset);
1785
+ }
1786
+ }
1787
+ return [...assetsBySourcePath.values()];
1788
+ }
1789
+ async function storeAsset(asset, adapter, skipExisting) {
1790
+ if (skipExisting && adapter.headObject) {
1791
+ const existing = await adapter.headObject(asset.storageKey);
1792
+ if (existing) {
1793
+ const existingUrl = existing.url ?? await adapter.getObjectUrl?.(asset.storageKey);
1794
+ if (existingUrl) return existingUrl;
1795
+ }
1796
+ }
1797
+ const writeResult = await adapter.writeObject({
1798
+ key: asset.storageKey,
1799
+ body: asset.body,
1800
+ contentType: asset.contentType,
1801
+ metadata: asset.metadata
1802
+ });
1803
+ return writeResult.url ?? await adapter.getObjectUrl?.(writeResult.key) ?? null;
1804
+ }
1805
+ function rewriteResultAssetUrls(result, assetUrlsByFilePath) {
1806
+ const chunks = result.chunks.map((chunk) => rewriteChunkAssetUrls(chunk, assetUrlsByFilePath));
1807
+ result.chunks.splice(0, result.chunks.length, ...chunks);
1808
+ refreshStaticChunkCollections(result);
1809
+ return result;
1810
+ }
1811
+ function rewriteChunkAssetUrls(chunk, assetUrlsByPath) {
1812
+ if (chunk.type === "image" || chunk.type === "table") {
1813
+ const sourcePath = normalizeResultAssetPath(chunk.filePath);
1814
+ const assetUrl = sourcePath ? assetUrlsByPath[sourcePath] : void 0;
1815
+ if (!assetUrl) return chunk;
1816
+ return {
1817
+ ...chunk,
1818
+ assetUrl
1819
+ };
1820
+ }
1821
+ if (chunk.type === "page") {
1822
+ const pageAssets = parsePageCitationAssets(chunk.metadata[pageCitationAssetsMetadataKey]);
1823
+ if (pageAssets.length === 0) return chunk;
1824
+ const rewrittenPageAssets = pageAssets.map((pageAsset) => {
1825
+ const sourcePath = normalizeResultAssetPath(pageAsset.artifactRef);
1826
+ const assetUrl = sourcePath ? assetUrlsByPath[sourcePath] : void 0;
1827
+ return assetUrl ? { ...pageAsset, assetUrl } : pageAsset;
1828
+ });
1829
+ return {
1830
+ ...chunk,
1831
+ metadata: {
1832
+ ...chunk.metadata,
1833
+ [pageCitationAssetsMetadataKey]: rewrittenPageAssets
1834
+ }
1835
+ };
1836
+ }
1837
+ return chunk;
1838
+ }
1839
+ function parsePageCitationAssets(value) {
1840
+ if (!Array.isArray(value)) return [];
1841
+ return value.flatMap((item) => {
1842
+ if (!isRecord(item)) return [];
1843
+ const pageNum = item.pageNum;
1844
+ const artifactRef = item.artifactRef;
1845
+ const contentType = item.contentType;
1846
+ const source = item.source;
1847
+ if (typeof pageNum !== "number" || !Number.isSafeInteger(pageNum) || pageNum <= 0) return [];
1848
+ if (typeof artifactRef !== "string") return [];
1849
+ if (contentType !== "image/png" && contentType !== "image/jpeg") return [];
1850
+ if (source !== "knowhere-rendered-page-citation-source") return [];
1851
+ return [
1852
+ {
1853
+ pageNum,
1854
+ artifactRef,
1855
+ assetUrl: typeof item.assetUrl === "string" ? item.assetUrl : void 0,
1856
+ contentType,
1857
+ width: typeof item.width === "number" ? item.width : void 0,
1858
+ height: typeof item.height === "number" ? item.height : void 0,
1859
+ source
1860
+ }
1861
+ ];
1862
+ });
1863
+ }
1864
+ function normalizeStorageKeyPrefix(value) {
1865
+ const normalized = normalizeStoragePath(value, { allowDotPrefix: false });
1866
+ if (!normalized) {
1867
+ throw new Error("storageAdapter keyPrefix must be a non-empty relative POSIX path.");
1868
+ }
1869
+ return normalized;
1870
+ }
1871
+ function normalizeResultAssetPath(value) {
1872
+ return normalizeStoragePath(value, { allowDotPrefix: true });
1873
+ }
1874
+ function normalizeStoragePath(value, options) {
1875
+ if (!value) return null;
1876
+ const trimmed = value.trim();
1877
+ if (trimmed.length === 0 || trimmed.includes("\0")) return null;
1878
+ if (trimmed.startsWith("/") || /^[A-Za-z]:/.test(trimmed)) return null;
1879
+ const normalized = options.allowDotPrefix ? trimmed.replaceAll("\\", "/").replace(/^\.\/+/, "") : trimmed.replaceAll("\\", "/");
1880
+ if (normalized.length === 0) return null;
1881
+ const parts = normalized.split("/");
1882
+ if (parts.some((part) => part.length === 0 || part === "." || part === "..")) return null;
1883
+ return parts.join("/");
1884
+ }
1885
+ function refreshStaticChunkCollections(result) {
1886
+ replaceStaticChunkCollection(
1887
+ result,
1888
+ "textChunks",
1889
+ result.chunks.filter((chunk) => chunk.type === "text")
1890
+ );
1891
+ replaceStaticChunkCollection(
1892
+ result,
1893
+ "imageChunks",
1894
+ result.chunks.filter((chunk) => chunk.type === "image")
1895
+ );
1896
+ replaceStaticChunkCollection(
1897
+ result,
1898
+ "tableChunks",
1899
+ result.chunks.filter((chunk) => chunk.type === "table")
1900
+ );
1901
+ replaceStaticChunkCollection(
1902
+ result,
1903
+ "pageChunks",
1904
+ result.chunks.filter((chunk) => chunk.type === "page")
1905
+ );
1906
+ }
1907
+ function replaceStaticChunkCollection(result, property, value) {
1908
+ const descriptor = Object.getOwnPropertyDescriptor(result, property);
1909
+ if (!descriptor || descriptor.get || descriptor.writable !== true) return;
1910
+ Object.defineProperty(result, property, {
1911
+ ...descriptor,
1912
+ value
1913
+ });
1914
+ }
1915
+ function getImageContentType(filePath) {
1916
+ const extension = filePath.split(".").at(-1)?.toLowerCase();
1917
+ if (extension === "jpg" || extension === "jpeg") return "image/jpeg";
1918
+ if (extension === "png") return "image/png";
1919
+ if (extension === "webp") return "image/webp";
1920
+ if (extension === "gif") return "image/gif";
1921
+ if (extension === "svg") return "image/svg+xml";
1922
+ return defaultImageContentType;
1923
+ }
1924
+ function isRecord(value) {
1925
+ return typeof value === "object" && value !== null;
1926
+ }
1927
+
1928
+ // src/storage/parsed-document-storage.ts
1929
+ var parsedStorageKeyPrefix = "parsed";
1930
+ var manifestStoragePath = "manifest/current.json";
1931
+ var indexStoragePath = "index.json";
1932
+ var chunkPagePathPattern = /^chunks\/page-(\d+)\.json$/;
1933
+ async function syncParseResultToParsedDocumentStorage(params) {
1934
+ const documentId = params.documentId ?? params.result.documentId ?? params.result.jobId;
1935
+ const revisionKey = params.revisionKey ?? params.result.jobId;
1936
+ const adapter = createParsedDocumentObjectStorageAdapter({
1937
+ storage: params.storage,
1938
+ documentId,
1939
+ revisionKey
1940
+ });
1941
+ const stored = await storeParseResultAssets(params.result, {
1942
+ adapter,
1943
+ keyPrefix: parsedStorageKeyPrefix,
1944
+ chunkPageSize: params.chunkPageSize,
1945
+ revisionKey
1946
+ });
1947
+ return {
1948
+ result: stored.result,
1949
+ snapshot: stored.snapshot,
1950
+ documentId,
1951
+ revisionKey
1952
+ };
1953
+ }
1954
+ function createParsedDocumentObjectStorageAdapter(params) {
1955
+ return {
1956
+ writeObject: (input) => writeParsedStorageObject(params, input),
1957
+ headObject: (key) => headParsedStorageObject(params, key),
1958
+ getObjectUrl: (key) => getParsedStorageObjectUrl(params, key)
1959
+ };
1960
+ }
1961
+ async function writeParsedStorageObject(params, input) {
1962
+ const storagePath = stripParsedStorageKeyPrefix(input.key);
1963
+ if (storagePath === manifestStoragePath) {
1964
+ const manifest = JSON.parse(
1965
+ Buffer.from(input.body).toString("utf8")
1966
+ );
1967
+ await params.storage.writeManifest({
1968
+ documentId: params.documentId,
1969
+ revisionKey: params.revisionKey,
1970
+ manifest
1971
+ });
1972
+ return { key: input.key };
1973
+ }
1974
+ if (storagePath === indexStoragePath) {
1975
+ return { key: input.key };
1976
+ }
1977
+ const chunkPageMatch = storagePath.match(chunkPagePathPattern);
1978
+ if (chunkPageMatch) {
1979
+ const page = JSON.parse(
1980
+ Buffer.from(input.body).toString("utf8")
1981
+ );
1982
+ await params.storage.writeChunkPage({
1983
+ documentId: params.documentId,
1984
+ revisionKey: params.revisionKey,
1985
+ page
1986
+ });
1987
+ return { key: input.key };
1988
+ }
1989
+ const stored = await params.storage.writeAsset({
1990
+ documentId: params.documentId,
1991
+ revisionKey: params.revisionKey,
1992
+ sourcePath: storagePath,
1993
+ body: input.body,
1994
+ contentType: input.contentType,
1995
+ metadata: input.metadata
1996
+ });
1997
+ return {
1998
+ key: input.key,
1999
+ url: stored.url
2000
+ };
2001
+ }
2002
+ async function headParsedStorageObject(params, key) {
2003
+ const storagePath = stripParsedStorageKeyPrefix(key);
2004
+ if (storagePath === manifestStoragePath || storagePath === indexStoragePath || chunkPagePathPattern.test(storagePath)) {
2005
+ return null;
2006
+ }
2007
+ const url = await params.storage.getAssetUrl({
2008
+ documentId: params.documentId,
2009
+ revisionKey: params.revisionKey,
2010
+ sourcePath: storagePath
2011
+ });
2012
+ return url ? {
2013
+ key,
2014
+ url
2015
+ } : null;
2016
+ }
2017
+ async function getParsedStorageObjectUrl(params, key) {
2018
+ const storagePath = stripParsedStorageKeyPrefix(key);
2019
+ if (storagePath === manifestStoragePath || storagePath === indexStoragePath || chunkPagePathPattern.test(storagePath)) {
2020
+ return null;
2021
+ }
2022
+ return params.storage.getAssetUrl({
2023
+ documentId: params.documentId,
2024
+ revisionKey: params.revisionKey,
2025
+ sourcePath: storagePath
2026
+ });
2027
+ }
2028
+ function stripParsedStorageKeyPrefix(key) {
2029
+ const prefix = `${parsedStorageKeyPrefix}/`;
2030
+ return key.startsWith(prefix) ? key.slice(prefix.length) : key;
2031
+ }
2032
+
1509
2033
  // src/knowledge/local-store.ts
1510
2034
  import { createHash } from "crypto";
1511
2035
  import os from "os";
@@ -1731,27 +2255,62 @@ function isMissingFileError2(error) {
1731
2255
  // src/knowledge/knowledge.ts
1732
2256
  var DEFAULT_READ_LIMIT = 12;
1733
2257
  var MAX_READ_LIMIT = 40;
2258
+ var DEFAULT_PAGE_SIZE = 20;
2259
+ var MAX_PAGE_SIZE = 200;
1734
2260
  var DEFAULT_GREP_LIMIT = 20;
1735
2261
  var MAX_GREP_LIMIT = 50;
1736
2262
  var DEFAULT_CONTEXT_CHARS = 80;
2263
+ var DEFAULT_REMOTE_SCAN_PAGE_SIZE = 100;
2264
+ var DEFAULT_MAX_SYNC_PAGES = 10;
2265
+ var DEFAULT_MAX_SYNC_ASSETS = 20;
2266
+ var DEFAULT_SYNC_DEADLINE_MS = 8e3;
2267
+ var DEFAULT_GREP_MAX_PAGES = 50;
2268
+ var DEFAULT_GREP_DEADLINE_MS = 8e3;
2269
+ var DEFAULT_OUTLINE_MAX_PAGES = 50;
2270
+ var DEFAULT_OUTLINE_DEADLINE_MS = 8e3;
1737
2271
  var SAFE_LOCAL_DOCUMENT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
1738
2272
  var REMOTE_DOCUMENT_ID_PATTERN = /^doc[_-]/;
1739
2273
  var Knowledge = class _Knowledge {
1740
2274
  client;
1741
2275
  store;
2276
+ cacheDirectory;
2277
+ parsedStorageConfig;
1742
2278
  constructor(client, options) {
1743
2279
  this.client = client;
2280
+ this.cacheDirectory = options?.cacheDirectory;
2281
+ this.parsedStorageConfig = options?.parsedStorage;
1744
2282
  this.store = new LocalKnowledgeStore(options?.cacheDirectory);
1745
2283
  }
1746
2284
  withCacheDirectory(cacheDirectory) {
1747
- return new _Knowledge(this.client, { cacheDirectory });
2285
+ return new _Knowledge(this.client, {
2286
+ cacheDirectory,
2287
+ parsedStorage: this.parsedStorageConfig
2288
+ });
1748
2289
  }
1749
- async parse(params) {
1750
- const result = await this.client.parse(params);
2290
+ withParsedStorage(parsedStorage) {
2291
+ return new _Knowledge(this.client, {
2292
+ cacheDirectory: this.cacheDirectory,
2293
+ parsedStorage
2294
+ });
2295
+ }
2296
+ async parseToLocalCache(params) {
2297
+ const loadedResult = params.storageAdapter ? await this.parseWithoutClientStorageAdapter(params) : await this.client.parse(params);
2298
+ const storedAssets = await storeParseResultAssets(loadedResult, params.storageAdapter);
2299
+ const syncedStorage = await this.syncParseResultToConfiguredStorage(storedAssets.result);
2300
+ const result = syncedStorage.result;
1751
2301
  const document = await this.store.saveResult(result, {
1752
2302
  localDocumentId: params.localDocumentId
1753
2303
  });
1754
- return { document, result };
2304
+ return {
2305
+ document,
2306
+ result,
2307
+ assetUrlsByFilePath: storedAssets.assetUrlsByFilePath,
2308
+ parsedSnapshot: storedAssets.snapshot
2309
+ };
2310
+ }
2311
+ /** @deprecated Use parseToLocalCache for local cache imports or the top-level client.parse for server-safe parsing. */
2312
+ async parse(params) {
2313
+ return this.parseToLocalCache(params);
1755
2314
  }
1756
2315
  async startParse(params) {
1757
2316
  const job = await this.client.startParse(params);
@@ -1782,14 +2341,100 @@ var Knowledge = class _Knowledge {
1782
2341
  results
1783
2342
  };
1784
2343
  }
1785
- async cacheJobResult(params) {
1786
- const result = await this.client.jobs.load(params.jobId, {
2344
+ async loadJobResult(params) {
2345
+ const loadedResult = await this.client.jobs.load(params.jobId, {
1787
2346
  verifyChecksum: params.verifyChecksum
1788
2347
  });
2348
+ const storedAssets = await storeParseResultAssets(loadedResult, params.storageAdapter);
2349
+ const syncedStorage = await this.syncParseResultToConfiguredStorage(storedAssets.result);
2350
+ const result = syncedStorage.result;
2351
+ return {
2352
+ result,
2353
+ assetUrlsByFilePath: storedAssets.assetUrlsByFilePath,
2354
+ parsedSnapshot: syncedStorage.snapshot ?? storedAssets.snapshot
2355
+ };
2356
+ }
2357
+ async importJobResult(params) {
2358
+ const stored = await this.loadJobResult(params);
2359
+ const result = stored.result;
1789
2360
  const document = await this.store.saveResult(result, {
1790
2361
  localDocumentId: params.localDocumentId
1791
2362
  });
1792
- return { document, result };
2363
+ return {
2364
+ document,
2365
+ result,
2366
+ assetUrlsByFilePath: stored.assetUrlsByFilePath,
2367
+ parsedSnapshot: stored.parsedSnapshot
2368
+ };
2369
+ }
2370
+ /** @deprecated Use importJobResult for local cache imports or loadJobResult for server-safe loads. */
2371
+ async cacheJobResult(params) {
2372
+ return this.importJobResult(params);
2373
+ }
2374
+ async syncParsedDocument(params) {
2375
+ const normalized = normalizeDocumentReference(params);
2376
+ const parsedStorageConfig = this.requireParsedStorageConfig();
2377
+ if (normalized.documentId) {
2378
+ return this.syncRemoteDocumentToParsedStorage({
2379
+ documentId: normalized.documentId,
2380
+ revisionKey: params.revisionKey,
2381
+ parsedStorageConfig
2382
+ });
2383
+ }
2384
+ if (normalized.jobId) {
2385
+ const loadedResult = await this.client.jobs.load(normalized.jobId);
2386
+ const synced2 = await syncParseResultToParsedDocumentStorage({
2387
+ result: loadedResult,
2388
+ storage: parsedStorageConfig.storage,
2389
+ documentId: loadedResult.documentId ?? normalized.localDocumentId ?? normalized.jobId,
2390
+ revisionKey: params.revisionKey ?? normalized.jobId,
2391
+ chunkPageSize: parsedStorageConfig.limits?.chunkPageSize
2392
+ });
2393
+ return {
2394
+ documentId: synced2.documentId,
2395
+ revisionKey: synced2.revisionKey,
2396
+ completed: true,
2397
+ manifest: synced2.snapshot?.manifest
2398
+ };
2399
+ }
2400
+ if (!normalized.localDocumentId) {
2401
+ throw new ValidationError("localDocumentId, documentId, or jobId is required");
2402
+ }
2403
+ const { result } = await this.store.loadResult(normalized.localDocumentId);
2404
+ const synced = await syncParseResultToParsedDocumentStorage({
2405
+ result,
2406
+ storage: parsedStorageConfig.storage,
2407
+ documentId: result.documentId ?? normalized.localDocumentId,
2408
+ revisionKey: params.revisionKey ?? result.jobId,
2409
+ chunkPageSize: parsedStorageConfig.limits?.chunkPageSize
2410
+ });
2411
+ return {
2412
+ documentId: synced.documentId,
2413
+ revisionKey: synced.revisionKey,
2414
+ completed: true,
2415
+ manifest: synced.snapshot?.manifest
2416
+ };
2417
+ }
2418
+ async syncParseResultToConfiguredStorage(result) {
2419
+ const parsedStorageConfig = this.parsedStorageConfig;
2420
+ if (!parsedStorageConfig) {
2421
+ return { result };
2422
+ }
2423
+ const synced = await syncParseResultToParsedDocumentStorage({
2424
+ result,
2425
+ storage: parsedStorageConfig.storage,
2426
+ chunkPageSize: parsedStorageConfig.limits?.chunkPageSize
2427
+ });
2428
+ return {
2429
+ result: synced.result,
2430
+ snapshot: synced.snapshot
2431
+ };
2432
+ }
2433
+ async parseWithoutClientStorageAdapter(params) {
2434
+ const parseParams = { ...params };
2435
+ delete parseParams.localDocumentId;
2436
+ delete parseParams.storageAdapter;
2437
+ return this.client.parse(parseParams);
1793
2438
  }
1794
2439
  async cacheDocument(params) {
1795
2440
  if (!params.documentId) {
@@ -1799,7 +2444,7 @@ var Knowledge = class _Knowledge {
1799
2444
  params.documentId
1800
2445
  );
1801
2446
  const jobId = await this.getPublishedDocumentJobId(params.documentId);
1802
- return this.cacheJobResult({
2447
+ return this.importJobResult({
1803
2448
  jobId,
1804
2449
  localDocumentId: params.localDocumentId ?? existing?.localDocumentId ?? createLocalDocumentIdForRemote(params.documentId)
1805
2450
  });
@@ -1836,7 +2481,7 @@ var Knowledge = class _Knowledge {
1836
2481
  };
1837
2482
  }
1838
2483
  try {
1839
- const cached = await this.cacheJobResult({
2484
+ const cached = await this.importJobResult({
1840
2485
  jobId,
1841
2486
  localDocumentId: trackedJob.localDocumentId
1842
2487
  });
@@ -1861,6 +2506,13 @@ var Knowledge = class _Knowledge {
1861
2506
  return this.store.listDocuments();
1862
2507
  }
1863
2508
  async getDocumentOutline(reference) {
2509
+ const normalized = normalizeDocumentReference(reference);
2510
+ if (normalized.documentId) {
2511
+ return this.getRemoteDocumentOutline(normalized.documentId, normalized.revisionKey);
2512
+ }
2513
+ if (normalized.localDocumentId && looksLikeRemoteDocumentId(normalized.localDocumentId) && !await this.store.getDocument(normalized.localDocumentId)) {
2514
+ return this.getRemoteDocumentOutline(normalized.localDocumentId, normalized.revisionKey);
2515
+ }
1864
2516
  const { document, result } = await this.loadReadableResult(reference);
1865
2517
  const chunks = indexChunks(result);
1866
2518
  const sections = buildFlatSections(result, chunks);
@@ -1876,6 +2528,14 @@ var Knowledge = class _Knowledge {
1876
2528
  };
1877
2529
  }
1878
2530
  async readChunks(params) {
2531
+ validateReadParams(params);
2532
+ const normalized = normalizeDocumentReference(params);
2533
+ if (normalized.documentId) {
2534
+ return this.readRemoteDocumentChunks(normalized.documentId, params);
2535
+ }
2536
+ if (normalized.localDocumentId && looksLikeRemoteDocumentId(normalized.localDocumentId) && !await this.store.getDocument(normalized.localDocumentId)) {
2537
+ return this.readRemoteDocumentChunks(normalized.localDocumentId, params);
2538
+ }
1879
2539
  const { document, result } = await this.loadReadableResult(params);
1880
2540
  const limit = clampLimit(params.limit, DEFAULT_READ_LIMIT, MAX_READ_LIMIT);
1881
2541
  const chunks = indexChunks(result).filter((chunk) => matchesReadScope(chunk, params));
@@ -1892,6 +2552,13 @@ var Knowledge = class _Knowledge {
1892
2552
  if (!params.pattern) {
1893
2553
  throw new ValidationError("pattern is required");
1894
2554
  }
2555
+ const normalized = normalizeDocumentReference(params);
2556
+ if (normalized.documentId) {
2557
+ return this.grepRemoteDocumentChunks(normalized.documentId, params);
2558
+ }
2559
+ if (normalized.localDocumentId && looksLikeRemoteDocumentId(normalized.localDocumentId) && !await this.store.getDocument(normalized.localDocumentId)) {
2560
+ return this.grepRemoteDocumentChunks(normalized.localDocumentId, params);
2561
+ }
1895
2562
  const { document, result } = await this.loadReadableResult(params);
1896
2563
  const maxResults = clampLimit(params.maxResults, DEFAULT_GREP_LIMIT, MAX_GREP_LIMIT);
1897
2564
  const contextChars = params.contextChars ?? DEFAULT_CONTEXT_CHARS;
@@ -1971,7 +2638,7 @@ var Knowledge = class _Knowledge {
1971
2638
  });
1972
2639
  }
1973
2640
  if (normalized.jobId) {
1974
- return this.cacheJobResult({
2641
+ return this.importJobResult({
1975
2642
  jobId: normalized.jobId,
1976
2643
  localDocumentId: normalized.localDocumentId
1977
2644
  });
@@ -1991,31 +2658,677 @@ var Knowledge = class _Knowledge {
1991
2658
  }
1992
2659
  throw new Error(`Local Knowhere document not found: ${normalized.localDocumentId}`);
1993
2660
  }
1994
- async findLocalDocumentByRemoteId(documentId) {
1995
- const documents = await this.store.listDocuments();
1996
- return documents.find((document) => document.documentId === documentId);
2661
+ async readRemoteDocumentChunks(documentId, params) {
2662
+ const explicitRevisionKey = params.revisionKey ?? params.jobId;
2663
+ if (explicitRevisionKey) {
2664
+ const stored2 = await this.tryReadChunksFromParsedStorage({
2665
+ documentId,
2666
+ revisionKey: explicitRevisionKey,
2667
+ params
2668
+ });
2669
+ if (stored2) {
2670
+ return stored2;
2671
+ }
2672
+ }
2673
+ if (hasScanReadFilters(params)) {
2674
+ return this.scanRemoteDocumentChunks(documentId, params);
2675
+ }
2676
+ const pageParams = getReadPageParams(params);
2677
+ const remotePage = await this.client.documents.listChunks(documentId, {
2678
+ page: pageParams.page,
2679
+ pageSize: pageParams.pageSize,
2680
+ chunkType: params.chunkType,
2681
+ includeAssetUrls: shouldRequestRemoteAssetUrls(
2682
+ params.assetUrlPolicy,
2683
+ this.parsedStorageConfig
2684
+ )
2685
+ });
2686
+ const revisionKey = getRemoteRevisionKey(remotePage);
2687
+ const stored = await this.tryReadChunksFromParsedStorage({
2688
+ documentId,
2689
+ revisionKey,
2690
+ params
2691
+ });
2692
+ if (stored) {
2693
+ return stored;
2694
+ }
2695
+ const chunks = await this.toRemoteReadChunks({
2696
+ documentId,
2697
+ revisionKey,
2698
+ chunks: remotePage.chunks,
2699
+ assetUrlPolicy: params.assetUrlPolicy
2700
+ });
2701
+ this.scheduleParsedStorageSync(documentId, revisionKey);
2702
+ return {
2703
+ document: createRemoteKnowledgeDocument({
2704
+ documentId,
2705
+ response: remotePage,
2706
+ revisionKey
2707
+ }),
2708
+ chunks,
2709
+ page: remotePage.pagination.page,
2710
+ pageSize: remotePage.pagination.pageSize,
2711
+ totalChunks: remotePage.pagination.total,
2712
+ totalPages: remotePage.pagination.totalPages
2713
+ };
1997
2714
  }
1998
- async getPublishedDocumentJobId(documentId) {
1999
- const response = await this.client.documents.listChunks(documentId, {
2715
+ async scanRemoteDocumentChunks(documentId, params) {
2716
+ const explicitRevisionKey = params.revisionKey ?? params.jobId;
2717
+ if (explicitRevisionKey) {
2718
+ const stored2 = await this.tryReadChunksFromParsedStorage({
2719
+ documentId,
2720
+ revisionKey: explicitRevisionKey,
2721
+ params
2722
+ });
2723
+ if (stored2) {
2724
+ return stored2;
2725
+ }
2726
+ }
2727
+ const limit = clampLimit(params.limit, DEFAULT_READ_LIMIT, MAX_READ_LIMIT);
2728
+ const limits = getParsedStorageLimits(this.parsedStorageConfig?.limits);
2729
+ const firstPage = await this.client.documents.listChunks(documentId, {
2000
2730
  page: 1,
2001
- pageSize: 1
2731
+ pageSize: limits.remotePageSize,
2732
+ chunkType: params.chunkType,
2733
+ includeAssetUrls: shouldRequestRemoteAssetUrls(
2734
+ params.assetUrlPolicy,
2735
+ this.parsedStorageConfig
2736
+ )
2002
2737
  });
2003
- if (!response.jobId) {
2004
- throw new Error(
2005
- `Cannot sync server document ${documentId}: current published job id was not returned.`
2006
- );
2738
+ const revisionKey = getRemoteRevisionKey(firstPage);
2739
+ const stored = await this.tryReadChunksFromParsedStorage({
2740
+ documentId,
2741
+ revisionKey,
2742
+ params
2743
+ });
2744
+ if (stored) {
2745
+ return stored;
2007
2746
  }
2008
- return response.jobId;
2009
- }
2010
- };
2011
- function normalizeDocumentReference(reference) {
2012
- if (typeof reference === "string") {
2013
- return { localDocumentId: reference };
2747
+ const selected = [];
2748
+ let scannedPages = 0;
2749
+ let page = 1;
2750
+ let response = firstPage;
2751
+ const startedAt = Date.now();
2752
+ while (true) {
2753
+ scannedPages += 1;
2754
+ selected.push(...response.chunks.filter((chunk) => matchesRemoteReadScope(chunk, params)));
2755
+ if (selected.length >= limit || response.pagination.page >= response.pagination.totalPages || scannedPages >= limits.outlineMaxPages || Date.now() - startedAt >= limits.outlineDeadlineMs) {
2756
+ break;
2757
+ }
2758
+ page += 1;
2759
+ response = await this.client.documents.listChunks(documentId, {
2760
+ page,
2761
+ pageSize: limits.remotePageSize,
2762
+ chunkType: params.chunkType,
2763
+ includeAssetUrls: shouldRequestRemoteAssetUrls(
2764
+ params.assetUrlPolicy,
2765
+ this.parsedStorageConfig
2766
+ )
2767
+ });
2768
+ }
2769
+ const chunks = await this.toRemoteReadChunks({
2770
+ documentId,
2771
+ revisionKey,
2772
+ chunks: selected.slice(0, limit),
2773
+ assetUrlPolicy: params.assetUrlPolicy
2774
+ });
2775
+ this.scheduleParsedStorageSync(documentId, revisionKey);
2776
+ return {
2777
+ document: createRemoteKnowledgeDocument({
2778
+ documentId,
2779
+ response: firstPage,
2780
+ revisionKey
2781
+ }),
2782
+ chunks,
2783
+ nextChunk: selected.length > limit ? toChunkPosition(selected[limit]) : void 0,
2784
+ totalChunks: firstPage.pagination.total,
2785
+ totalPages: firstPage.pagination.totalPages
2786
+ };
2014
2787
  }
2015
- return reference;
2016
- }
2017
- function looksLikeRemoteDocumentId(value) {
2018
- return REMOTE_DOCUMENT_ID_PATTERN.test(value);
2788
+ async grepRemoteDocumentChunks(documentId, params) {
2789
+ const explicitRevisionKey = params.revisionKey ?? params.jobId;
2790
+ if (explicitRevisionKey && !params.continuationCursor) {
2791
+ const stored2 = await this.tryGrepChunksFromParsedStorage({
2792
+ documentId,
2793
+ revisionKey: explicitRevisionKey,
2794
+ params
2795
+ });
2796
+ if (stored2) {
2797
+ return stored2;
2798
+ }
2799
+ }
2800
+ const maxResults = clampLimit(params.maxResults, DEFAULT_GREP_LIMIT, MAX_GREP_LIMIT);
2801
+ const contextChars = params.contextChars ?? DEFAULT_CONTEXT_CHARS;
2802
+ const matcher = createMatcher(params);
2803
+ const limits = getParsedStorageLimits(this.parsedStorageConfig?.limits);
2804
+ const cursor = parseContinuationCursor(params.continuationCursor);
2805
+ const startPage = cursor?.documentId === documentId ? cursor.nextPage : 1;
2806
+ const firstPage = await this.client.documents.listChunks(documentId, {
2807
+ page: startPage,
2808
+ pageSize: limits.remotePageSize,
2809
+ chunkType: params.chunkType,
2810
+ includeAssetUrls: false
2811
+ });
2812
+ const revisionKey = getRemoteRevisionKey(firstPage);
2813
+ const stored = startPage === 1 ? await this.tryGrepChunksFromParsedStorage({
2814
+ documentId,
2815
+ revisionKey,
2816
+ params
2817
+ }) : null;
2818
+ if (stored) {
2819
+ return stored;
2820
+ }
2821
+ const matches = [];
2822
+ let scannedChunks = 0;
2823
+ let scannedPages = 0;
2824
+ let response = firstPage;
2825
+ let page = startPage;
2826
+ const startedAt = Date.now();
2827
+ while (true) {
2828
+ scannedPages += 1;
2829
+ for (const [chunkIndex, chunk] of response.chunks.entries()) {
2830
+ if (response.pagination.page === startPage && cursor?.nextChunkIndex !== void 0 && chunkIndex < cursor.nextChunkIndex) {
2831
+ continue;
2832
+ }
2833
+ if (!matchesGrepScope(toIndexedRemoteChunk(chunk), params)) {
2834
+ continue;
2835
+ }
2836
+ scannedChunks += 1;
2837
+ const chunkMatches = matcher(chunk.content ?? "");
2838
+ for (const [matchIndex, match] of chunkMatches.entries()) {
2839
+ if (response.pagination.page === startPage && cursor?.nextChunkIndex === chunkIndex && cursor.nextMatchIndex !== void 0 && matchIndex < cursor.nextMatchIndex) {
2840
+ continue;
2841
+ }
2842
+ matches.push({
2843
+ position: toChunkPosition(chunk),
2844
+ chunkId: chunk.chunkId,
2845
+ chunkType: chunk.chunkType,
2846
+ sectionPath: chunk.sectionPath ?? "",
2847
+ sourceChunkPath: chunk.sourceChunkPath ?? chunk.sectionPath ?? "",
2848
+ filePath: chunk.filePath ?? void 0,
2849
+ startOffset: match.startOffset,
2850
+ endOffset: match.endOffset,
2851
+ snippet: buildSnippet(
2852
+ chunk.content ?? "",
2853
+ match.startOffset,
2854
+ match.endOffset,
2855
+ contextChars
2856
+ )
2857
+ });
2858
+ if (matches.length >= maxResults) {
2859
+ const continuationCursor = createGrepContinuationCursor({
2860
+ documentId,
2861
+ revisionKey,
2862
+ response,
2863
+ chunkIndex,
2864
+ matchIndex,
2865
+ chunkMatchCount: chunkMatches.length
2866
+ });
2867
+ this.scheduleParsedStorageSync(documentId, revisionKey);
2868
+ return {
2869
+ document: createRemoteKnowledgeDocument({
2870
+ documentId,
2871
+ response: firstPage,
2872
+ revisionKey
2873
+ }),
2874
+ matches,
2875
+ scannedChunks,
2876
+ truncated: continuationCursor !== void 0,
2877
+ continuationCursor
2878
+ };
2879
+ }
2880
+ }
2881
+ }
2882
+ const shouldStop = response.pagination.page >= response.pagination.totalPages || scannedPages >= limits.grepMaxPages || Date.now() - startedAt >= limits.grepDeadlineMs;
2883
+ if (shouldStop) {
2884
+ const truncated = response.pagination.page < response.pagination.totalPages;
2885
+ this.scheduleParsedStorageSync(documentId, revisionKey);
2886
+ return {
2887
+ document: createRemoteKnowledgeDocument({
2888
+ documentId,
2889
+ response: firstPage,
2890
+ revisionKey
2891
+ }),
2892
+ matches,
2893
+ scannedChunks,
2894
+ truncated,
2895
+ continuationCursor: truncated ? createContinuationCursor({
2896
+ documentId,
2897
+ revisionKey,
2898
+ nextPage: response.pagination.page + 1
2899
+ }) : void 0
2900
+ };
2901
+ }
2902
+ page += 1;
2903
+ response = await this.client.documents.listChunks(documentId, {
2904
+ page,
2905
+ pageSize: limits.remotePageSize,
2906
+ chunkType: params.chunkType,
2907
+ includeAssetUrls: false
2908
+ });
2909
+ }
2910
+ }
2911
+ async getRemoteDocumentOutline(documentId, revisionKeyOverride) {
2912
+ if (revisionKeyOverride) {
2913
+ const stored2 = await this.tryReadOutlineFromParsedStorage(documentId, revisionKeyOverride);
2914
+ if (stored2) {
2915
+ return stored2;
2916
+ }
2917
+ }
2918
+ const limits = getParsedStorageLimits(this.parsedStorageConfig?.limits);
2919
+ const firstPage = await this.client.documents.listChunks(documentId, {
2920
+ page: 1,
2921
+ pageSize: limits.remotePageSize,
2922
+ includeAssetUrls: false
2923
+ });
2924
+ const revisionKey = getRemoteRevisionKey(firstPage);
2925
+ const stored = await this.tryReadOutlineFromParsedStorage(documentId, revisionKey);
2926
+ if (stored) {
2927
+ return stored;
2928
+ }
2929
+ const chunks = [];
2930
+ let page = 1;
2931
+ let response = firstPage;
2932
+ let scannedPages = 0;
2933
+ const startedAt = Date.now();
2934
+ while (true) {
2935
+ scannedPages += 1;
2936
+ chunks.push(...response.chunks.map(toIndexedRemoteChunk));
2937
+ if (response.pagination.page >= response.pagination.totalPages || scannedPages >= limits.outlineMaxPages || Date.now() - startedAt >= limits.outlineDeadlineMs) {
2938
+ break;
2939
+ }
2940
+ page += 1;
2941
+ response = await this.client.documents.listChunks(documentId, {
2942
+ page,
2943
+ pageSize: limits.remotePageSize,
2944
+ includeAssetUrls: false
2945
+ });
2946
+ }
2947
+ const truncated = response.pagination.page < response.pagination.totalPages;
2948
+ const sections = buildFlatSectionsFromIndexedChunks(chunks);
2949
+ this.scheduleParsedStorageSync(documentId, revisionKey);
2950
+ return {
2951
+ document: createRemoteKnowledgeDocument({
2952
+ documentId,
2953
+ response: firstPage,
2954
+ revisionKey,
2955
+ typeCounts: countIndexedTypes(chunks)
2956
+ }),
2957
+ totalChunks: firstPage.pagination.total,
2958
+ typeCounts: countIndexedTypes(chunks),
2959
+ sections,
2960
+ sectionTree: nestSections(sections),
2961
+ truncated,
2962
+ continuationCursor: truncated ? createContinuationCursor({
2963
+ documentId,
2964
+ revisionKey,
2965
+ nextPage: response.pagination.page + 1
2966
+ }) : void 0
2967
+ };
2968
+ }
2969
+ async tryReadChunksFromParsedStorage(params) {
2970
+ const parsedStorageConfig = this.parsedStorageConfig;
2971
+ if (!parsedStorageConfig) {
2972
+ return null;
2973
+ }
2974
+ const manifest = await this.readFreshParsedStorageManifest(
2975
+ params.documentId,
2976
+ params.revisionKey
2977
+ );
2978
+ if (!manifest) {
2979
+ return null;
2980
+ }
2981
+ const pageParams = getReadPageParams(params.params);
2982
+ const indexedChunks = await this.readStoredSnapshotChunks({
2983
+ documentId: params.documentId,
2984
+ revisionKey: params.revisionKey,
2985
+ manifest,
2986
+ chunkType: params.params.chunkType
2987
+ });
2988
+ if (!indexedChunks) {
2989
+ return null;
2990
+ }
2991
+ const scopedChunks = indexedChunks.filter((chunk) => matchesReadScope(chunk, params.params));
2992
+ const selected = hasScanReadFilters(params.params) ? selectReadWindow(
2993
+ scopedChunks,
2994
+ params.params,
2995
+ clampLimit(params.params.limit, DEFAULT_READ_LIMIT, MAX_READ_LIMIT)
2996
+ ) : scopedChunks.slice(
2997
+ (pageParams.page - 1) * pageParams.pageSize,
2998
+ pageParams.page * pageParams.pageSize
2999
+ );
3000
+ const visibleChunks = applyAssetUrlPolicyToReadChunks(
3001
+ selected.map(toReadChunk),
3002
+ params.params.assetUrlPolicy
3003
+ );
3004
+ const totalChunks = scopedChunks.length;
3005
+ return {
3006
+ document: createStoredKnowledgeDocument({
3007
+ documentId: params.documentId,
3008
+ manifest,
3009
+ revisionKey: params.revisionKey,
3010
+ typeCounts: countIndexedTypes(indexedChunks)
3011
+ }),
3012
+ chunks: visibleChunks,
3013
+ page: hasScanReadFilters(params.params) ? void 0 : pageParams.page,
3014
+ pageSize: hasScanReadFilters(params.params) ? void 0 : pageParams.pageSize,
3015
+ totalChunks,
3016
+ totalPages: Math.max(1, Math.ceil(totalChunks / pageParams.pageSize)),
3017
+ nextChunk: getNextChunkPosition(scopedChunks, selected)
3018
+ };
3019
+ }
3020
+ async tryGrepChunksFromParsedStorage(params) {
3021
+ const parsedStorageConfig = this.parsedStorageConfig;
3022
+ if (!parsedStorageConfig) {
3023
+ return null;
3024
+ }
3025
+ const manifest = await this.readFreshParsedStorageManifest(
3026
+ params.documentId,
3027
+ params.revisionKey
3028
+ );
3029
+ if (!manifest) {
3030
+ return null;
3031
+ }
3032
+ const indexedChunks = await this.readStoredSnapshotChunks({
3033
+ documentId: params.documentId,
3034
+ revisionKey: params.revisionKey,
3035
+ manifest,
3036
+ chunkType: params.params.chunkType
3037
+ });
3038
+ if (!indexedChunks) {
3039
+ return null;
3040
+ }
3041
+ const maxResults = clampLimit(params.params.maxResults, DEFAULT_GREP_LIMIT, MAX_GREP_LIMIT);
3042
+ const contextChars = params.params.contextChars ?? DEFAULT_CONTEXT_CHARS;
3043
+ const matcher = createMatcher(params.params);
3044
+ const scopedChunks = indexedChunks.filter((chunk) => matchesGrepScope(chunk, params.params));
3045
+ const matches = [];
3046
+ let scannedChunks = 0;
3047
+ for (const chunk of scopedChunks) {
3048
+ scannedChunks += 1;
3049
+ for (const match of matcher(chunk.content)) {
3050
+ matches.push({
3051
+ position: chunk.position,
3052
+ chunkId: chunk.chunkId,
3053
+ chunkType: chunk.chunkType,
3054
+ sectionPath: chunk.sectionPath,
3055
+ sourceChunkPath: chunk.sourceChunkPath,
3056
+ filePath: chunk.filePath,
3057
+ startOffset: match.startOffset,
3058
+ endOffset: match.endOffset,
3059
+ snippet: buildSnippet(chunk.content, match.startOffset, match.endOffset, contextChars)
3060
+ });
3061
+ if (matches.length >= maxResults) {
3062
+ return {
3063
+ document: createStoredKnowledgeDocument({
3064
+ documentId: params.documentId,
3065
+ manifest,
3066
+ revisionKey: params.revisionKey,
3067
+ typeCounts: countIndexedTypes(indexedChunks)
3068
+ }),
3069
+ matches,
3070
+ scannedChunks,
3071
+ truncated: true
3072
+ };
3073
+ }
3074
+ }
3075
+ }
3076
+ return {
3077
+ document: createStoredKnowledgeDocument({
3078
+ documentId: params.documentId,
3079
+ manifest,
3080
+ revisionKey: params.revisionKey,
3081
+ typeCounts: countIndexedTypes(indexedChunks)
3082
+ }),
3083
+ matches,
3084
+ scannedChunks,
3085
+ truncated: false
3086
+ };
3087
+ }
3088
+ async tryReadOutlineFromParsedStorage(documentId, revisionKey) {
3089
+ const manifest = await this.readFreshParsedStorageManifest(documentId, revisionKey);
3090
+ if (!manifest) {
3091
+ return null;
3092
+ }
3093
+ const indexedChunks = await this.readStoredSnapshotChunks({
3094
+ documentId,
3095
+ revisionKey,
3096
+ manifest
3097
+ });
3098
+ if (!indexedChunks) {
3099
+ return null;
3100
+ }
3101
+ const sections = buildFlatSectionsFromIndexedChunks(indexedChunks);
3102
+ const typeCounts = countIndexedTypes(indexedChunks);
3103
+ return {
3104
+ document: createStoredKnowledgeDocument({
3105
+ documentId,
3106
+ manifest,
3107
+ revisionKey,
3108
+ typeCounts
3109
+ }),
3110
+ totalChunks: manifest.totalChunks,
3111
+ typeCounts,
3112
+ sections,
3113
+ sectionTree: nestSections(sections),
3114
+ truncated: false
3115
+ };
3116
+ }
3117
+ async readFreshParsedStorageManifest(documentId, revisionKey) {
3118
+ const parsedStorageConfig = this.parsedStorageConfig;
3119
+ if (!parsedStorageConfig) {
3120
+ return null;
3121
+ }
3122
+ const manifest = await parsedStorageConfig.storage.readManifest({
3123
+ documentId,
3124
+ revisionKey
3125
+ });
3126
+ if (!manifest || !isFreshManifest(manifest, revisionKey)) {
3127
+ return null;
3128
+ }
3129
+ return manifest;
3130
+ }
3131
+ async readStoredSnapshotChunks(params) {
3132
+ const parsedStorageConfig = this.parsedStorageConfig;
3133
+ if (!parsedStorageConfig) {
3134
+ return null;
3135
+ }
3136
+ const chunks = [];
3137
+ for (const pageReference of params.manifest.chunkPages) {
3138
+ const page = await parsedStorageConfig.storage.readChunkPage({
3139
+ documentId: params.documentId,
3140
+ revisionKey: params.revisionKey,
3141
+ page: pageReference.page,
3142
+ chunkType: params.chunkType
3143
+ });
3144
+ if (!page) {
3145
+ return null;
3146
+ }
3147
+ chunks.push(
3148
+ ...page.chunks.filter((chunk) => !params.chunkType || chunk.chunkType === params.chunkType).map(toIndexedSnapshotChunk)
3149
+ );
3150
+ }
3151
+ return chunks.sort((left, right) => left.position - right.position);
3152
+ }
3153
+ async toRemoteReadChunks(params) {
3154
+ const readChunks = params.chunks.map((chunk) => toRemoteReadChunk(chunk));
3155
+ if (params.assetUrlPolicy === "durable") {
3156
+ return this.hardenReadChunkAssetUrls({
3157
+ documentId: params.documentId,
3158
+ revisionKey: params.revisionKey,
3159
+ chunks: readChunks
3160
+ });
3161
+ }
3162
+ return applyAssetUrlPolicyToReadChunks(readChunks, params.assetUrlPolicy);
3163
+ }
3164
+ async hardenReadChunkAssetUrls(params) {
3165
+ const parsedStorageConfig = this.parsedStorageConfig;
3166
+ if (!parsedStorageConfig) {
3167
+ return applyAssetUrlPolicyToReadChunks(params.chunks, "none");
3168
+ }
3169
+ const hardenedChunks = [];
3170
+ for (const chunk of params.chunks) {
3171
+ hardenedChunks.push(
3172
+ await hardenReadChunkAssetUrls({
3173
+ storageConfig: parsedStorageConfig,
3174
+ documentId: params.documentId,
3175
+ revisionKey: params.revisionKey,
3176
+ chunk
3177
+ })
3178
+ );
3179
+ }
3180
+ return hardenedChunks;
3181
+ }
3182
+ scheduleParsedStorageSync(documentId, revisionKey) {
3183
+ const parsedStorageConfig = this.parsedStorageConfig;
3184
+ if (!parsedStorageConfig) {
3185
+ return;
3186
+ }
3187
+ const scheduler = parsedStorageConfig.scheduler ?? defaultParsedStorageScheduler;
3188
+ const task = async () => {
3189
+ try {
3190
+ await this.syncRemoteDocumentToParsedStorage({
3191
+ documentId,
3192
+ revisionKey,
3193
+ parsedStorageConfig
3194
+ });
3195
+ } catch (error) {
3196
+ await parsedStorageConfig.storage.writeSyncProgress({
3197
+ documentId,
3198
+ revisionKey,
3199
+ nextChunkPage: 1,
3200
+ nextAssetIndex: 0,
3201
+ status: "failed",
3202
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3203
+ error: error instanceof Error ? error.message : String(error)
3204
+ });
3205
+ }
3206
+ };
3207
+ void Promise.resolve(scheduler.schedule(task)).catch(() => void 0);
3208
+ }
3209
+ async syncRemoteDocumentToParsedStorage(params) {
3210
+ const limits = getParsedStorageLimits(params.parsedStorageConfig.limits);
3211
+ const existingProgress = params.revisionKey ? await params.parsedStorageConfig.storage.readSyncProgress({
3212
+ documentId: params.documentId,
3213
+ revisionKey: params.revisionKey
3214
+ }) : null;
3215
+ let nextPage = existingProgress?.status === "running" ? existingProgress.nextChunkPage : 1;
3216
+ let response = await this.client.documents.listChunks(params.documentId, {
3217
+ page: nextPage,
3218
+ pageSize: limits.remotePageSize,
3219
+ includeAssetUrls: true
3220
+ });
3221
+ const revisionKey = params.revisionKey ?? getRemoteRevisionKey(response);
3222
+ if (params.revisionKey && getRemoteRevisionKey(response) !== params.revisionKey) {
3223
+ response = await this.client.documents.listChunks(params.documentId, {
3224
+ page: 1,
3225
+ pageSize: limits.remotePageSize,
3226
+ includeAssetUrls: true
3227
+ });
3228
+ nextPage = 1;
3229
+ }
3230
+ const totalPages = response.pagination.totalPages;
3231
+ const chunkPages = [];
3232
+ let syncedPages = 0;
3233
+ const startedAt = Date.now();
3234
+ while (nextPage <= totalPages) {
3235
+ if (syncedPages > 0) {
3236
+ response = await this.client.documents.listChunks(params.documentId, {
3237
+ page: nextPage,
3238
+ pageSize: limits.remotePageSize,
3239
+ includeAssetUrls: true
3240
+ });
3241
+ }
3242
+ const readChunks = await this.hardenReadChunkAssetUrls({
3243
+ documentId: params.documentId,
3244
+ revisionKey,
3245
+ chunks: response.chunks.map(toRemoteReadChunk)
3246
+ });
3247
+ const page = toSnapshotChunkPageFromReadChunks({
3248
+ response,
3249
+ revisionKey,
3250
+ chunks: readChunks
3251
+ });
3252
+ await params.parsedStorageConfig.storage.writeChunkPage({
3253
+ documentId: params.documentId,
3254
+ revisionKey,
3255
+ page
3256
+ });
3257
+ chunkPages.push(page);
3258
+ nextPage += 1;
3259
+ syncedPages += 1;
3260
+ await params.parsedStorageConfig.storage.writeSyncProgress({
3261
+ documentId: params.documentId,
3262
+ revisionKey,
3263
+ nextChunkPage: nextPage,
3264
+ nextAssetIndex: 0,
3265
+ status: "running",
3266
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3267
+ });
3268
+ if (syncedPages >= limits.maxPagesPerSync || Date.now() - startedAt >= limits.syncDeadlineMs) {
3269
+ return {
3270
+ documentId: params.documentId,
3271
+ revisionKey,
3272
+ completed: false
3273
+ };
3274
+ }
3275
+ }
3276
+ const manifest = createRemoteSnapshotManifest({
3277
+ response,
3278
+ revisionKey,
3279
+ chunkPages
3280
+ });
3281
+ await params.parsedStorageConfig.storage.writeManifest({
3282
+ documentId: params.documentId,
3283
+ revisionKey,
3284
+ manifest
3285
+ });
3286
+ await params.parsedStorageConfig.storage.writeSyncProgress({
3287
+ documentId: params.documentId,
3288
+ revisionKey,
3289
+ nextChunkPage: totalPages + 1,
3290
+ nextAssetIndex: 0,
3291
+ status: "completed",
3292
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3293
+ });
3294
+ return {
3295
+ documentId: params.documentId,
3296
+ revisionKey,
3297
+ completed: true,
3298
+ manifest
3299
+ };
3300
+ }
3301
+ requireParsedStorageConfig() {
3302
+ if (!this.parsedStorageConfig) {
3303
+ throw new ValidationError("parsed document storage is not configured");
3304
+ }
3305
+ return this.parsedStorageConfig;
3306
+ }
3307
+ async findLocalDocumentByRemoteId(documentId) {
3308
+ const documents = await this.store.listDocuments();
3309
+ return documents.find((document) => document.documentId === documentId);
3310
+ }
3311
+ async getPublishedDocumentJobId(documentId) {
3312
+ const response = await this.client.documents.listChunks(documentId, {
3313
+ page: 1,
3314
+ pageSize: 1
3315
+ });
3316
+ if (!response.jobId) {
3317
+ throw new Error(
3318
+ `Cannot sync server document ${documentId}: current published job id was not returned.`
3319
+ );
3320
+ }
3321
+ return response.jobId;
3322
+ }
3323
+ };
3324
+ function normalizeDocumentReference(reference) {
3325
+ if (typeof reference === "string") {
3326
+ return { localDocumentId: reference };
3327
+ }
3328
+ return reference;
3329
+ }
3330
+ function looksLikeRemoteDocumentId(value) {
3331
+ return REMOTE_DOCUMENT_ID_PATTERN.test(value);
2019
3332
  }
2020
3333
  function createLocalDocumentIdForRemote(documentId) {
2021
3334
  if (SAFE_LOCAL_DOCUMENT_ID_PATTERN.test(documentId) && !documentId.includes("..") && !documentId.includes("/") && !documentId.includes("\\")) {
@@ -2024,6 +3337,488 @@ function createLocalDocumentIdForRemote(documentId) {
2024
3337
  const hash = createHash2("sha256").update(documentId).digest("hex").slice(0, 16);
2025
3338
  return `remote_${hash}`;
2026
3339
  }
3340
+ var defaultParsedStorageScheduler = {
3341
+ schedule(task) {
3342
+ void task();
3343
+ }
3344
+ };
3345
+ function getParsedStorageLimits(limits) {
3346
+ return {
3347
+ chunkPageSize: clampLimit(limits?.chunkPageSize, DEFAULT_REMOTE_SCAN_PAGE_SIZE, MAX_PAGE_SIZE),
3348
+ remotePageSize: clampLimit(
3349
+ limits?.remotePageSize,
3350
+ DEFAULT_REMOTE_SCAN_PAGE_SIZE,
3351
+ MAX_PAGE_SIZE
3352
+ ),
3353
+ maxPagesPerSync: clampLimit(limits?.maxPagesPerSync, DEFAULT_MAX_SYNC_PAGES, 1e3),
3354
+ maxAssetsPerSync: clampLimit(limits?.maxAssetsPerSync, DEFAULT_MAX_SYNC_ASSETS, 1e3),
3355
+ syncDeadlineMs: clampLimit(limits?.syncDeadlineMs, DEFAULT_SYNC_DEADLINE_MS, 6e4),
3356
+ grepMaxPages: clampLimit(limits?.grepMaxPages, DEFAULT_GREP_MAX_PAGES, 1e3),
3357
+ grepDeadlineMs: clampLimit(limits?.grepDeadlineMs, DEFAULT_GREP_DEADLINE_MS, 6e4),
3358
+ outlineMaxPages: clampLimit(limits?.outlineMaxPages, DEFAULT_OUTLINE_MAX_PAGES, 1e3),
3359
+ outlineDeadlineMs: clampLimit(limits?.outlineDeadlineMs, DEFAULT_OUTLINE_DEADLINE_MS, 6e4)
3360
+ };
3361
+ }
3362
+ function validateReadParams(params) {
3363
+ const usesPagedDisplay = params.page !== void 0 || params.pageSize !== void 0 || params.assetUrlPolicy !== void 0;
3364
+ if (!usesPagedDisplay) {
3365
+ return;
3366
+ }
3367
+ if (params.sectionPath !== void 0 || params.startChunk !== void 0 || params.endChunk !== void 0 || params.chunkId !== void 0) {
3368
+ throw new ValidationError(
3369
+ "page, pageSize, and assetUrlPolicy cannot be combined with sectionPath, startChunk, endChunk, or chunkId"
3370
+ );
3371
+ }
3372
+ }
3373
+ function hasScanReadFilters(params) {
3374
+ return params.sectionPath !== void 0 || params.startChunk !== void 0 || params.endChunk !== void 0 || params.chunkId !== void 0;
3375
+ }
3376
+ function getReadPageParams(params) {
3377
+ return {
3378
+ page: clampLimit(params.page, 1, Number.MAX_SAFE_INTEGER),
3379
+ pageSize: clampLimit(params.pageSize ?? params.limit, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE)
3380
+ };
3381
+ }
3382
+ function shouldRequestRemoteAssetUrls(assetUrlPolicy, parsedStorageConfig) {
3383
+ return assetUrlPolicy === "durable" && parsedStorageConfig !== void 0;
3384
+ }
3385
+ function getRemoteRevisionKey(response) {
3386
+ const revisionKey = response.jobResultId ?? response.jobId;
3387
+ if (!revisionKey) {
3388
+ throw new Error(
3389
+ `Cannot read server document ${response.documentId}: current published revision was not returned.`
3390
+ );
3391
+ }
3392
+ return revisionKey;
3393
+ }
3394
+ function createRemoteKnowledgeDocument(params) {
3395
+ return {
3396
+ localDocumentId: params.documentId,
3397
+ jobId: params.response.jobId ?? params.revisionKey,
3398
+ documentId: params.documentId,
3399
+ namespace: params.response.namespace,
3400
+ sourceFileName: params.documentId,
3401
+ chunkCount: params.response.pagination.total,
3402
+ typeCounts: params.typeCounts ?? countRemoteChunkTypes(params.response.chunks),
3403
+ resultDirectoryPath: `remote:${params.documentId}`,
3404
+ createdAt: /* @__PURE__ */ new Date(0),
3405
+ updatedAt: /* @__PURE__ */ new Date(0)
3406
+ };
3407
+ }
3408
+ function createStoredKnowledgeDocument(params) {
3409
+ return {
3410
+ localDocumentId: params.documentId,
3411
+ jobId: params.manifest.jobId || params.revisionKey,
3412
+ documentId: params.manifest.documentId ?? params.documentId,
3413
+ namespace: params.manifest.namespace,
3414
+ sourceFileName: params.manifest.sourceFileName,
3415
+ chunkCount: params.manifest.totalChunks,
3416
+ typeCounts: params.typeCounts,
3417
+ resultDirectoryPath: `parsed-storage:${params.documentId}`,
3418
+ createdAt: new Date(params.manifest.createdAt),
3419
+ updatedAt: new Date(params.manifest.createdAt)
3420
+ };
3421
+ }
3422
+ function isFreshManifest(manifest, revisionKey) {
3423
+ return (manifest.revisionKey ?? manifest.jobId) === revisionKey;
3424
+ }
3425
+ function toRemoteReadChunk(chunk) {
3426
+ const metadata = cloneMetadata(chunk.metadata);
3427
+ return {
3428
+ position: toChunkPosition(chunk),
3429
+ chunkId: chunk.chunkId,
3430
+ chunkType: chunk.chunkType,
3431
+ contentSource: chunk.contentSource ?? void 0,
3432
+ content: chunk.content ?? "",
3433
+ readableContent: getReadableRemoteChunkContent(chunk, metadata),
3434
+ sectionPath: chunk.sectionPath ?? "",
3435
+ sourceChunkPath: chunk.sourceChunkPath ?? chunk.sectionPath ?? "",
3436
+ filePath: chunk.filePath ?? void 0,
3437
+ assetUrl: chunk.assetUrl ?? void 0,
3438
+ pageNumbers: getChunkPageNumbers(metadata),
3439
+ metadata
3440
+ };
3441
+ }
3442
+ function getReadableRemoteChunkContent(chunk, metadata) {
3443
+ if (chunk.chunkType !== "page") {
3444
+ return chunk.content ?? "";
3445
+ }
3446
+ const summary = metadata.summary;
3447
+ return typeof summary === "string" && summary.trim().length > 0 ? summary : chunk.content ?? "";
3448
+ }
3449
+ function toIndexedRemoteChunk(chunk) {
3450
+ const readChunk = toRemoteReadChunk(chunk);
3451
+ return toIndexedReadChunk(readChunk);
3452
+ }
3453
+ function toIndexedSnapshotChunk(chunk) {
3454
+ return toIndexedReadChunk({
3455
+ position: chunk.sortOrder,
3456
+ chunkId: chunk.chunkId,
3457
+ chunkType: chunk.chunkType,
3458
+ contentSource: chunk.contentSource,
3459
+ content: chunk.content,
3460
+ readableContent: chunk.chunkType === "page" && typeof chunk.metadata.summary === "string" && chunk.metadata.summary.trim().length > 0 ? chunk.metadata.summary : chunk.content,
3461
+ sectionPath: chunk.sectionPath ?? "",
3462
+ sourceChunkPath: chunk.sourceChunkPath,
3463
+ filePath: chunk.filePath,
3464
+ assetUrl: chunk.assetUrl,
3465
+ pageNumbers: getChunkPageNumbers(chunk.metadata),
3466
+ metadata: cloneMetadata(chunk.metadata)
3467
+ });
3468
+ }
3469
+ function toIndexedReadChunk(chunk) {
3470
+ return {
3471
+ source: createSyntheticChunk(chunk),
3472
+ position: chunk.position,
3473
+ chunkId: chunk.chunkId,
3474
+ chunkType: chunk.chunkType,
3475
+ contentSource: chunk.contentSource,
3476
+ content: chunk.content,
3477
+ readableContent: chunk.readableContent,
3478
+ sectionPath: chunk.sectionPath,
3479
+ sourceChunkPath: chunk.sourceChunkPath,
3480
+ filePath: chunk.filePath,
3481
+ assetUrl: chunk.assetUrl,
3482
+ pageNumbers: chunk.pageNumbers,
3483
+ metadata: chunk.metadata
3484
+ };
3485
+ }
3486
+ function createSyntheticChunk(chunk) {
3487
+ return {
3488
+ chunkId: chunk.chunkId,
3489
+ type: chunk.chunkType,
3490
+ contentSource: chunk.contentSource,
3491
+ content: chunk.content,
3492
+ path: chunk.sourceChunkPath,
3493
+ metadata: chunk.metadata
3494
+ };
3495
+ }
3496
+ function toChunkPosition(chunk) {
3497
+ return chunk ? chunk.sortOrder + 1 : 1;
3498
+ }
3499
+ function buildFlatSectionsFromIndexedChunks(chunks) {
3500
+ const byPath = /* @__PURE__ */ new Map();
3501
+ for (const chunk of chunks) {
3502
+ const path4 = chunk.sectionPath || chunk.sourceChunkPath;
3503
+ const existing = byPath.get(path4);
3504
+ if (existing) {
3505
+ addChunkToSection(existing, chunk);
3506
+ } else {
3507
+ byPath.set(path4, createSectionFromChunk(path4, chunk));
3508
+ }
3509
+ }
3510
+ return [...byPath.values()].sort(compareSections);
3511
+ }
3512
+ function matchesRemoteReadScope(chunk, params) {
3513
+ return matchesReadScope(toIndexedRemoteChunk(chunk), params);
3514
+ }
3515
+ function getNextChunkPosition(chunks, selected) {
3516
+ const lastSelected = selected[selected.length - 1];
3517
+ if (!lastSelected || selected.length >= chunks.length) {
3518
+ return void 0;
3519
+ }
3520
+ return chunks[chunks.indexOf(lastSelected) + 1]?.position;
3521
+ }
3522
+ function applyAssetUrlPolicyToReadChunks(chunks, assetUrlPolicy) {
3523
+ if (assetUrlPolicy !== "none") {
3524
+ return chunks.map((chunk) => ({
3525
+ ...chunk,
3526
+ metadata: cloneMetadata(chunk.metadata)
3527
+ }));
3528
+ }
3529
+ return chunks.map((chunk) => ({
3530
+ ...chunk,
3531
+ assetUrl: void 0,
3532
+ metadata: removeAssetUrlsFromMetadata(chunk.metadata)
3533
+ }));
3534
+ }
3535
+ async function hardenReadChunkAssetUrls(params) {
3536
+ const metadata = cloneMetadata(params.chunk.metadata);
3537
+ const assetUrl = await hardenPrimaryAssetUrl(params, metadata);
3538
+ const pageAssets = await hardenPageAssetUrls(params, metadata);
3539
+ if (pageAssets) {
3540
+ metadata.pageAssets = pageAssets;
3541
+ }
3542
+ return {
3543
+ ...params.chunk,
3544
+ assetUrl,
3545
+ metadata
3546
+ };
3547
+ }
3548
+ async function hardenPrimaryAssetUrl(params, metadata) {
3549
+ if (!params.chunk.assetUrl || !params.chunk.filePath) {
3550
+ return void 0;
3551
+ }
3552
+ return writeDurableAssetUrl({
3553
+ storageConfig: params.storageConfig,
3554
+ documentId: params.documentId,
3555
+ revisionKey: params.revisionKey,
3556
+ sourcePath: params.chunk.filePath,
3557
+ sourceUrl: params.chunk.assetUrl,
3558
+ fallbackContentType: inferContentTypeFromPath(params.chunk.filePath),
3559
+ metadata: {
3560
+ chunkId: params.chunk.chunkId,
3561
+ chunkType: params.chunk.chunkType,
3562
+ sourcePath: params.chunk.filePath,
3563
+ ...stringifyFlatMetadata(metadata)
3564
+ }
3565
+ });
3566
+ }
3567
+ async function hardenPageAssetUrls(params, metadata) {
3568
+ const pageAssets = toUnknownArray(metadata.pageAssets);
3569
+ if (!pageAssets) {
3570
+ return void 0;
3571
+ }
3572
+ const hardenedAssets = [];
3573
+ for (const pageAsset of pageAssets) {
3574
+ if (!isRecord2(pageAsset)) {
3575
+ hardenedAssets.push(pageAsset);
3576
+ continue;
3577
+ }
3578
+ const artifactRef = pageAsset.artifactRef;
3579
+ const sourceUrl = pageAsset.assetUrl;
3580
+ if (typeof artifactRef !== "string" || typeof sourceUrl !== "string") {
3581
+ hardenedAssets.push(removeAssetUrlField(pageAsset));
3582
+ continue;
3583
+ }
3584
+ const durableUrl = await writeDurableAssetUrl({
3585
+ storageConfig: params.storageConfig,
3586
+ documentId: params.documentId,
3587
+ revisionKey: params.revisionKey,
3588
+ sourcePath: artifactRef,
3589
+ sourceUrl,
3590
+ fallbackContentType: typeof pageAsset.contentType === "string" ? pageAsset.contentType : inferContentTypeFromPath(artifactRef),
3591
+ metadata: {
3592
+ chunkId: params.chunk.chunkId,
3593
+ chunkType: params.chunk.chunkType,
3594
+ sourcePath: artifactRef
3595
+ }
3596
+ });
3597
+ const rest = removeAssetUrlField(pageAsset);
3598
+ hardenedAssets.push(durableUrl ? { ...rest, assetUrl: durableUrl } : rest);
3599
+ }
3600
+ return hardenedAssets;
3601
+ }
3602
+ async function writeDurableAssetUrl(params) {
3603
+ const existingUrl = await params.storageConfig.storage.getAssetUrl({
3604
+ documentId: params.documentId,
3605
+ revisionKey: params.revisionKey,
3606
+ sourcePath: params.sourcePath
3607
+ });
3608
+ if (existingUrl) {
3609
+ return existingUrl;
3610
+ }
3611
+ try {
3612
+ const fetched = await fetchAsset(params.sourceUrl, params.fallbackContentType);
3613
+ const written = await params.storageConfig.storage.writeAsset({
3614
+ documentId: params.documentId,
3615
+ revisionKey: params.revisionKey,
3616
+ sourcePath: params.sourcePath,
3617
+ body: fetched.body,
3618
+ contentType: fetched.contentType,
3619
+ metadata: params.metadata
3620
+ });
3621
+ return written.url ?? await params.storageConfig.storage.getAssetUrl({
3622
+ documentId: params.documentId,
3623
+ revisionKey: params.revisionKey,
3624
+ sourcePath: params.sourcePath
3625
+ }) ?? void 0;
3626
+ } catch {
3627
+ return void 0;
3628
+ }
3629
+ }
3630
+ async function fetchAsset(sourceUrl, fallbackContentType) {
3631
+ const response = await fetch(sourceUrl);
3632
+ if (!response.ok) {
3633
+ throw new Error(`Unable to fetch parsed asset: ${response.status}`);
3634
+ }
3635
+ return {
3636
+ body: new Uint8Array(await response.arrayBuffer()),
3637
+ contentType: response.headers.get("content-type") ?? fallbackContentType
3638
+ };
3639
+ }
3640
+ function removeAssetUrlsFromMetadata(metadata) {
3641
+ const cloned = cloneMetadata(metadata);
3642
+ const pageAssets = toUnknownArray(cloned.pageAssets);
3643
+ if (!pageAssets) {
3644
+ return cloned;
3645
+ }
3646
+ cloned.pageAssets = pageAssets.map((pageAsset) => {
3647
+ if (!isRecord2(pageAsset)) {
3648
+ return pageAsset;
3649
+ }
3650
+ return removeAssetUrlField(pageAsset);
3651
+ });
3652
+ return cloned;
3653
+ }
3654
+ function removeAssetUrlField(value) {
3655
+ const cloned = { ...value };
3656
+ delete cloned.assetUrl;
3657
+ return cloned;
3658
+ }
3659
+ function toSnapshotChunkPageFromReadChunks(params) {
3660
+ return {
3661
+ version: 1,
3662
+ jobId: params.response.jobId ?? params.revisionKey,
3663
+ revisionKey: params.revisionKey,
3664
+ documentId: params.response.documentId,
3665
+ namespace: params.response.namespace,
3666
+ sourceFileName: params.response.documentId,
3667
+ page: params.response.pagination.page,
3668
+ pageSize: params.response.pagination.pageSize,
3669
+ total: params.response.pagination.total,
3670
+ totalPages: params.response.pagination.totalPages,
3671
+ chunks: params.chunks.map(toSnapshotChunkFromReadChunk)
3672
+ };
3673
+ }
3674
+ function toSnapshotChunkFromReadChunk(chunk) {
3675
+ return {
3676
+ id: chunk.chunkId,
3677
+ chunkId: chunk.chunkId,
3678
+ chunkType: chunk.chunkType,
3679
+ contentSource: chunk.contentSource,
3680
+ content: chunk.content,
3681
+ sectionPath: chunk.sectionPath,
3682
+ sourceChunkPath: chunk.sourceChunkPath,
3683
+ filePath: chunk.filePath,
3684
+ sortOrder: chunk.position,
3685
+ metadata: cloneMetadata(chunk.metadata),
3686
+ assetUrl: chunk.assetUrl
3687
+ };
3688
+ }
3689
+ function createRemoteSnapshotManifest(params) {
3690
+ return {
3691
+ version: 1,
3692
+ kind: "knowhere-parsed-result-snapshot",
3693
+ jobId: params.response.jobId ?? params.revisionKey,
3694
+ revisionKey: params.revisionKey,
3695
+ documentId: params.response.documentId,
3696
+ namespace: params.response.namespace,
3697
+ sourceFileName: params.response.documentId,
3698
+ totalChunks: params.response.pagination.total,
3699
+ typeCounts: countSnapshotPagesTypes(params.chunkPages),
3700
+ chunkPageSize: params.response.pagination.pageSize,
3701
+ chunkPages: Array.from({ length: params.response.pagination.totalPages }, (_, index) => ({
3702
+ page: index + 1,
3703
+ pageSize: params.response.pagination.pageSize,
3704
+ chunkCount: index + 1 === params.response.pagination.totalPages ? params.response.pagination.total - params.response.pagination.pageSize * (params.response.pagination.totalPages - 1) : params.response.pagination.pageSize,
3705
+ key: `parsed/chunks/page-${index + 1}.json`
3706
+ })),
3707
+ assetUrlsByFilePath: {},
3708
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
3709
+ };
3710
+ }
3711
+ function countSnapshotPagesTypes(pages) {
3712
+ return pages.flatMap((page) => page.chunks).reduce(
3713
+ (counts, chunk) => {
3714
+ const chunkType = chunk.chunkType;
3715
+ counts[chunkType] += 1;
3716
+ return counts;
3717
+ },
3718
+ { text: 0, image: 0, table: 0, page: 0 }
3719
+ );
3720
+ }
3721
+ function countRemoteChunkTypes(chunks) {
3722
+ return chunks.reduce(
3723
+ (counts, chunk) => {
3724
+ counts[chunk.chunkType] += 1;
3725
+ return counts;
3726
+ },
3727
+ { text: 0, image: 0, table: 0, page: 0 }
3728
+ );
3729
+ }
3730
+ function createContinuationCursor(cursor) {
3731
+ return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
3732
+ }
3733
+ function createGrepContinuationCursor(params) {
3734
+ if (params.matchIndex + 1 < params.chunkMatchCount) {
3735
+ return createContinuationCursor({
3736
+ documentId: params.documentId,
3737
+ revisionKey: params.revisionKey,
3738
+ nextPage: params.response.pagination.page,
3739
+ nextChunkIndex: params.chunkIndex,
3740
+ nextMatchIndex: params.matchIndex + 1
3741
+ });
3742
+ }
3743
+ if (params.chunkIndex + 1 < params.response.chunks.length) {
3744
+ return createContinuationCursor({
3745
+ documentId: params.documentId,
3746
+ revisionKey: params.revisionKey,
3747
+ nextPage: params.response.pagination.page,
3748
+ nextChunkIndex: params.chunkIndex + 1,
3749
+ nextMatchIndex: 0
3750
+ });
3751
+ }
3752
+ if (params.response.pagination.page < params.response.pagination.totalPages) {
3753
+ return createContinuationCursor({
3754
+ documentId: params.documentId,
3755
+ revisionKey: params.revisionKey,
3756
+ nextPage: params.response.pagination.page + 1,
3757
+ nextChunkIndex: 0,
3758
+ nextMatchIndex: 0
3759
+ });
3760
+ }
3761
+ return void 0;
3762
+ }
3763
+ function parseContinuationCursor(value) {
3764
+ if (!value) {
3765
+ return null;
3766
+ }
3767
+ try {
3768
+ const parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
3769
+ if (!isRecord2(parsed)) {
3770
+ return null;
3771
+ }
3772
+ const documentId = parsed.documentId;
3773
+ const revisionKey = parsed.revisionKey;
3774
+ const nextPage = parsed.nextPage;
3775
+ const nextChunkIndex = parsed.nextChunkIndex;
3776
+ const nextMatchIndex = parsed.nextMatchIndex;
3777
+ if (typeof documentId !== "string" || typeof revisionKey !== "string" || typeof nextPage !== "number" || !Number.isInteger(nextPage) || nextPage < 1 || !isOptionalNonNegativeInteger(nextChunkIndex) || !isOptionalNonNegativeInteger(nextMatchIndex)) {
3778
+ return null;
3779
+ }
3780
+ return {
3781
+ documentId,
3782
+ revisionKey,
3783
+ nextPage,
3784
+ nextChunkIndex,
3785
+ nextMatchIndex
3786
+ };
3787
+ } catch {
3788
+ return null;
3789
+ }
3790
+ }
3791
+ function isOptionalNonNegativeInteger(value) {
3792
+ return value === void 0 || typeof value === "number" && Number.isInteger(value) && value >= 0;
3793
+ }
3794
+ function cloneMetadata(metadata) {
3795
+ return { ...metadata };
3796
+ }
3797
+ function stringifyFlatMetadata(metadata) {
3798
+ const entries = Object.entries(metadata).flatMap(([key, value]) => {
3799
+ if (typeof value === "string") {
3800
+ return [[key, value]];
3801
+ }
3802
+ if (typeof value === "number" || typeof value === "boolean") {
3803
+ return [[key, String(value)]];
3804
+ }
3805
+ return [];
3806
+ });
3807
+ return Object.fromEntries(entries);
3808
+ }
3809
+ function toUnknownArray(value) {
3810
+ return Array.isArray(value) ? value : null;
3811
+ }
3812
+ function inferContentTypeFromPath(filePath) {
3813
+ const extension = filePath.split(".").at(-1)?.toLowerCase();
3814
+ if (extension === "png") return "image/png";
3815
+ if (extension === "jpg" || extension === "jpeg") return "image/jpeg";
3816
+ if (extension === "html" || extension === "htm") return "text/html; charset=utf-8";
3817
+ return "application/octet-stream";
3818
+ }
3819
+ function isRecord2(value) {
3820
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3821
+ }
2027
3822
  function indexChunks(result) {
2028
3823
  return result.chunks.map((chunk, index) => {
2029
3824
  const filePath = getChunkFilePath2(chunk);
@@ -2039,6 +3834,7 @@ function indexChunks(result) {
2039
3834
  sectionPath: normalizeSectionPath(chunk.path, result.manifest.sourceFileName),
2040
3835
  sourceChunkPath: chunk.path,
2041
3836
  filePath,
3837
+ assetUrl: getChunkAssetUrl(chunk),
2042
3838
  pageNumbers: getChunkPageNumbers(chunk.metadata),
2043
3839
  metadata: chunk.metadata
2044
3840
  };
@@ -2052,12 +3848,11 @@ function getReadableChunkContent(chunk) {
2052
3848
  return typeof summary === "string" && summary.trim().length > 0 ? summary : chunk.content;
2053
3849
  }
2054
3850
  function getChunkPageNumbers(metadata) {
2055
- const value = metadata.pageNums;
2056
- if (!Array.isArray(value)) {
2057
- return void 0;
2058
- }
2059
- const pageNumbers = value.filter(
2060
- (pageNumber) => Number.isInteger(pageNumber) && pageNumber > 0
3851
+ const values = [metadata.pageNums, metadata.page_nums].filter(Array.isArray);
3852
+ const pageNumbers = values.flatMap(
3853
+ (value) => value.filter(
3854
+ (pageNumber) => Number.isInteger(pageNumber) && pageNumber > 0
3855
+ )
2061
3856
  );
2062
3857
  return pageNumbers.length > 0 ? [...new Set(pageNumbers)].sort((left, right) => left - right) : void 0;
2063
3858
  }
@@ -2068,14 +3863,17 @@ function getChunkFilePath2(chunk) {
2068
3863
  const filePath = chunk.metadata.filePath;
2069
3864
  return typeof filePath === "string" ? filePath : void 0;
2070
3865
  }
2071
- function normalizeSectionPath(path3, sourceFileName) {
2072
- if (!path3) {
3866
+ function getChunkAssetUrl(chunk) {
3867
+ return chunk.type === "image" || chunk.type === "table" ? chunk.assetUrl : void 0;
3868
+ }
3869
+ function normalizeSectionPath(path4, sourceFileName) {
3870
+ if (!path4) {
2073
3871
  return "";
2074
3872
  }
2075
- if (path3.startsWith("images/") || path3.startsWith("tables/")) {
2076
- return path3;
3873
+ if (path4.startsWith("images/") || path4.startsWith("tables/")) {
3874
+ return path4;
2077
3875
  }
2078
- const parts = path3.split("/").filter(Boolean);
3876
+ const parts = path4.split("/").filter(Boolean);
2079
3877
  if (sourceFileName) {
2080
3878
  const fileNameIndex = parts.indexOf(sourceFileName);
2081
3879
  if (fileNameIndex >= 0) {
@@ -2097,12 +3895,12 @@ function buildFlatSections(result, chunks) {
2097
3895
  }
2098
3896
  const byPath = /* @__PURE__ */ new Map();
2099
3897
  for (const chunk of chunks) {
2100
- const path3 = chunk.sectionPath || chunk.sourceChunkPath;
2101
- const existing = byPath.get(path3);
3898
+ const path4 = chunk.sectionPath || chunk.sourceChunkPath;
3899
+ const existing = byPath.get(path4);
2102
3900
  if (existing) {
2103
3901
  addChunkToSection(existing, chunk);
2104
3902
  } else {
2105
- byPath.set(path3, createSectionFromChunk(path3, chunk));
3903
+ byPath.set(path4, createSectionFromChunk(path4, chunk));
2106
3904
  }
2107
3905
  }
2108
3906
  return [...byPath.values()].sort(compareSections);
@@ -2236,6 +4034,7 @@ function toReadChunk(chunk) {
2236
4034
  sectionPath: chunk.sectionPath,
2237
4035
  sourceChunkPath: chunk.sourceChunkPath,
2238
4036
  filePath: chunk.filePath,
4037
+ assetUrl: chunk.assetUrl,
2239
4038
  pageNumbers: chunk.pageNumbers,
2240
4039
  metadata: chunk.metadata
2241
4040
  };
@@ -2412,7 +4211,12 @@ var Knowhere = class {
2412
4211
  const result = await this.jobs.load(jobResult, {
2413
4212
  verifyChecksum: params.verifyChecksum
2414
4213
  });
2415
- return enrichParseResult(result, jobResult);
4214
+ const enrichedResult = enrichParseResult(result, {
4215
+ namespace: jobResult.namespace,
4216
+ documentId: jobResult.documentId ?? params.documentId
4217
+ });
4218
+ const storedResult = await storeParseResultAssets(enrichedResult, params.storageAdapter);
4219
+ return storedResult.result;
2416
4220
  }
2417
4221
  /**
2418
4222
  * Start a parse job and return immediately after the URL job is created or
@@ -2455,12 +4259,152 @@ var Knowhere = class {
2455
4259
  return job;
2456
4260
  }
2457
4261
  };
4262
+
4263
+ // src/storage/disk-parsed-document-storage.ts
4264
+ import { createHash as createHash3 } from "crypto";
4265
+ import { promises as fs4 } from "fs";
4266
+ import path3 from "path";
4267
+ var DiskParsedDocumentStorage = class {
4268
+ rootDirectory;
4269
+ constructor(rootDirectory) {
4270
+ this.rootDirectory = rootDirectory;
4271
+ }
4272
+ async readManifest(params) {
4273
+ return this.readOptionalJson(
4274
+ this.getManifestPath(params.documentId, params.revisionKey)
4275
+ );
4276
+ }
4277
+ async writeManifest(params) {
4278
+ await this.writeJson(
4279
+ this.getManifestPath(params.documentId, params.revisionKey),
4280
+ params.manifest
4281
+ );
4282
+ }
4283
+ async readChunkPage(params) {
4284
+ return this.readOptionalJson(
4285
+ this.getChunkPagePath(params.documentId, params.revisionKey, params.page)
4286
+ );
4287
+ }
4288
+ async writeChunkPage(params) {
4289
+ await this.writeJson(
4290
+ this.getChunkPagePath(params.documentId, params.revisionKey, params.page.page),
4291
+ params.page
4292
+ );
4293
+ }
4294
+ async writeAsset(params) {
4295
+ const assetPath = this.getAssetPath(params.documentId, params.revisionKey, params.sourcePath);
4296
+ await fs4.mkdir(path3.dirname(assetPath), { recursive: true });
4297
+ await fs4.writeFile(assetPath, params.body);
4298
+ await this.writeJson(`${assetPath}.metadata.json`, {
4299
+ contentType: params.contentType,
4300
+ metadata: params.metadata ?? {}
4301
+ });
4302
+ return {
4303
+ sourcePath: params.sourcePath,
4304
+ url: toFileUrl(assetPath)
4305
+ };
4306
+ }
4307
+ async getAssetUrl(params) {
4308
+ const assetPath = this.getAssetPath(params.documentId, params.revisionKey, params.sourcePath);
4309
+ try {
4310
+ await fs4.access(assetPath);
4311
+ return toFileUrl(assetPath);
4312
+ } catch (error) {
4313
+ if (isMissingFileError3(error)) {
4314
+ return null;
4315
+ }
4316
+ throw error;
4317
+ }
4318
+ }
4319
+ async readSyncProgress(params) {
4320
+ return this.readOptionalJson(
4321
+ this.getProgressPath(params.documentId, params.revisionKey)
4322
+ );
4323
+ }
4324
+ async writeSyncProgress(params) {
4325
+ await this.writeJson(this.getProgressPath(params.documentId, params.revisionKey), params);
4326
+ }
4327
+ getManifestPath(documentId, revisionKey) {
4328
+ return path3.join(
4329
+ this.getRevisionDirectory(documentId, revisionKey),
4330
+ "manifest",
4331
+ "current.json"
4332
+ );
4333
+ }
4334
+ getChunkPagePath(documentId, revisionKey, page) {
4335
+ return path3.join(
4336
+ this.getRevisionDirectory(documentId, revisionKey),
4337
+ "chunks",
4338
+ `page-${page}.json`
4339
+ );
4340
+ }
4341
+ getProgressPath(documentId, revisionKey) {
4342
+ return path3.join(this.getRevisionDirectory(documentId, revisionKey), "sync-progress.json");
4343
+ }
4344
+ getAssetPath(documentId, revisionKey, sourcePath) {
4345
+ const normalizedSourcePath = normalizeRelativeStoragePath(sourcePath);
4346
+ const assetPath = path3.resolve(
4347
+ this.getRevisionDirectory(documentId, revisionKey),
4348
+ "assets",
4349
+ normalizedSourcePath
4350
+ );
4351
+ const assetsDirectory = path3.resolve(
4352
+ this.getRevisionDirectory(documentId, revisionKey),
4353
+ "assets"
4354
+ );
4355
+ if (!isPathInsideDirectory2(assetPath, assetsDirectory)) {
4356
+ throw new Error(`Parsed asset path resolves outside storage: ${sourcePath}`);
4357
+ }
4358
+ return assetPath;
4359
+ }
4360
+ getRevisionDirectory(documentId, revisionKey) {
4361
+ return path3.join(this.rootDirectory, hashPathPart(documentId), hashPathPart(revisionKey));
4362
+ }
4363
+ async readOptionalJson(filePath) {
4364
+ try {
4365
+ return JSON.parse(await fs4.readFile(filePath, "utf8"));
4366
+ } catch (error) {
4367
+ if (isMissingFileError3(error)) {
4368
+ return null;
4369
+ }
4370
+ throw error;
4371
+ }
4372
+ }
4373
+ async writeJson(filePath, value) {
4374
+ await fs4.mkdir(path3.dirname(filePath), { recursive: true });
4375
+ await fs4.writeFile(filePath, JSON.stringify(value, null, 2), {
4376
+ encoding: "utf8",
4377
+ flag: "w"
4378
+ });
4379
+ }
4380
+ };
4381
+ function hashPathPart(value) {
4382
+ return createHash3("sha256").update(value).digest("hex").slice(0, 24);
4383
+ }
4384
+ function normalizeRelativeStoragePath(value) {
4385
+ const normalized = value.replaceAll("\\", "/").replace(/^\.\/+/, "");
4386
+ if (normalized.length === 0 || normalized.includes("\0") || normalized.startsWith("/") || normalized.split("/").some((part) => part.length === 0 || part === "." || part === "..")) {
4387
+ throw new Error(`Invalid parsed storage path: ${value}`);
4388
+ }
4389
+ return normalized;
4390
+ }
4391
+ function isPathInsideDirectory2(filePath, directory) {
4392
+ const relative = path3.relative(directory, filePath);
4393
+ return relative === "" || !relative.startsWith("..") && !path3.isAbsolute(relative);
4394
+ }
4395
+ function toFileUrl(filePath) {
4396
+ return `file://${filePath}`;
4397
+ }
4398
+ function isMissingFileError3(error) {
4399
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
4400
+ }
2458
4401
  export {
2459
4402
  APIError,
2460
4403
  AuthenticationError,
2461
4404
  BadRequestError,
2462
4405
  ChecksumError,
2463
4406
  ConflictError,
4407
+ DiskParsedDocumentStorage,
2464
4408
  Documents,
2465
4409
  GatewayTimeoutError,
2466
4410
  InternalServerError,
@@ -2482,5 +4426,7 @@ export {
2482
4426
  TimeoutError,
2483
4427
  VERSION,
2484
4428
  ValidationError,
2485
- Knowhere as default
4429
+ Knowhere as default,
4430
+ storeParseResultAssets,
4431
+ syncParseResultToParsedDocumentStorage
2486
4432
  };