@ontos-ai/knowhere-sdk 2.0.0 → 2.1.2

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.js CHANGED
@@ -35,6 +35,7 @@ __export(index_exports, {
35
35
  BadRequestError: () => BadRequestError,
36
36
  ChecksumError: () => ChecksumError,
37
37
  ConflictError: () => ConflictError,
38
+ DiskParsedDocumentStorage: () => DiskParsedDocumentStorage,
38
39
  Documents: () => Documents,
39
40
  GatewayTimeoutError: () => GatewayTimeoutError,
40
41
  InternalServerError: () => InternalServerError,
@@ -56,7 +57,9 @@ __export(index_exports, {
56
57
  TimeoutError: () => TimeoutError,
57
58
  VERSION: () => VERSION,
58
59
  ValidationError: () => ValidationError,
59
- default: () => Knowhere
60
+ default: () => Knowhere,
61
+ storeParseResultAssets: () => storeParseResultAssets,
62
+ syncParseResultToParsedDocumentStorage: () => syncParseResultToParsedDocumentStorage
60
63
  });
61
64
  module.exports = __toCommonJS(index_exports);
62
65
 
@@ -369,8 +372,8 @@ function enrichParseResult(parseResult2, scope) {
369
372
  }
370
373
  return parseResult2;
371
374
  }
372
- function sanitizePath(path3) {
373
- let sanitized = path3.replace(/^\/+/, "");
375
+ function sanitizePath(path4) {
376
+ let sanitized = path4.replace(/^\/+/, "");
374
377
  sanitized = sanitized.replace(/\.\.(\/|\\)/g, "");
375
378
  sanitized = sanitized.replace(/\\/g, "/");
376
379
  return sanitized;
@@ -756,8 +759,8 @@ var BaseResource = class {
756
759
  constructor(httpClient) {
757
760
  this.httpClient = httpClient;
758
761
  }
759
- endpoint(path3) {
760
- return `/v2${path3.startsWith("/") ? path3 : `/${path3}`}`;
762
+ endpoint(path4) {
763
+ return `/v2${path4.startsWith("/") ? path4 : `/${path4}`}`;
761
764
  }
762
765
  };
763
766
 
@@ -973,17 +976,28 @@ async function saveExpandedParseResult(result, directory) {
973
976
  if (result.rawZip.length > 0) {
974
977
  const didExtractZip = await tryExtractRawZip(result.rawZip, directory);
975
978
  if (didExtractZip) {
979
+ await writeExpandedParseResultMetadata(result, directory);
976
980
  return directory;
977
981
  }
978
982
  }
979
983
  await import_fs2.promises.mkdir(directory, { recursive: true });
984
+ await writeExpandedParseResultMetadata(result, directory);
985
+ for (const imageChunk of result.imageChunks) {
986
+ await writeBinaryAsset(directory, imageChunk.filePath, imageChunk.data);
987
+ }
988
+ for (const tableChunk of result.tableChunks) {
989
+ await writeTextAsset(directory, tableChunk.filePath, tableChunk.html);
990
+ }
991
+ return directory;
992
+ }
993
+ async function writeExpandedParseResultMetadata(result, directory) {
980
994
  await import_fs2.promises.writeFile((0, import_path.join)(directory, "manifest.json"), JSON.stringify(result.manifest, null, 2));
981
995
  if (result.docNav) {
982
996
  await import_fs2.promises.writeFile((0, import_path.join)(directory, "doc_nav.json"), JSON.stringify(result.docNav, null, 2));
983
997
  }
984
998
  await import_fs2.promises.writeFile(
985
999
  (0, import_path.join)(directory, "chunks.json"),
986
- JSON.stringify(serializeChunks(result.chunks), null, 2)
1000
+ JSON.stringify(keysToSnake(serializeChunks(result.chunks)), null, 2)
987
1001
  );
988
1002
  if (result.chunksSlim) {
989
1003
  await import_fs2.promises.writeFile(
@@ -1012,13 +1026,6 @@ async function saveExpandedParseResult(result, directory) {
1012
1026
  if (result.hierarchyViewHtml) {
1013
1027
  await import_fs2.promises.writeFile((0, import_path.join)(directory, "hierarchy_view.html"), result.hierarchyViewHtml);
1014
1028
  }
1015
- for (const imageChunk of result.imageChunks) {
1016
- await writeBinaryAsset(directory, imageChunk.filePath, imageChunk.data);
1017
- }
1018
- for (const tableChunk of result.tableChunks) {
1019
- await writeTextAsset(directory, tableChunk.filePath, tableChunk.html);
1020
- }
1021
- return directory;
1022
1029
  }
1023
1030
  async function tryExtractRawZip(zipBuffer, directory) {
1024
1031
  try {
@@ -1157,13 +1164,14 @@ function buildTextChunk(chunkData) {
1157
1164
  };
1158
1165
  }
1159
1166
  function buildPageChunk(chunkData) {
1167
+ const metadata = chunkData.metadata ?? {};
1160
1168
  return {
1161
1169
  chunkId: chunkData.chunkId ?? "",
1162
1170
  type: "page",
1163
1171
  contentSource: chunkData.contentSource ?? "summary",
1164
1172
  content: chunkData.content ?? "",
1165
1173
  path: chunkData.path ?? "",
1166
- metadata: chunkData.metadata ?? {}
1174
+ metadata
1167
1175
  };
1168
1176
  }
1169
1177
  function buildImageChunk(chunkData, filePath, imageBuffer) {
@@ -1174,6 +1182,7 @@ function buildImageChunk(chunkData, filePath, imageBuffer) {
1174
1182
  content: chunkData.content ?? "",
1175
1183
  path: chunkData.path ?? "",
1176
1184
  filePath,
1185
+ assetUrl: chunkData.assetUrl,
1177
1186
  data: imageBuffer,
1178
1187
  metadata: chunkData.metadata ?? {},
1179
1188
  get format() {
@@ -1192,6 +1201,7 @@ function buildTableChunk(chunkData, filePath, html) {
1192
1201
  content: chunkData.content ?? "",
1193
1202
  path: chunkData.path ?? "",
1194
1203
  filePath,
1204
+ assetUrl: chunkData.assetUrl,
1195
1205
  html,
1196
1206
  metadata: chunkData.metadata ?? {},
1197
1207
  async save(directory) {
@@ -1286,6 +1296,7 @@ function serializeChunks(chunks) {
1286
1296
  };
1287
1297
  if (chunk.type === "image" || chunk.type === "table") {
1288
1298
  rawChunk.filePath = chunk.filePath;
1299
+ rawChunk.assetUrl = chunk.assetUrl;
1289
1300
  }
1290
1301
  return rawChunk;
1291
1302
  })
@@ -1494,6 +1505,14 @@ var Documents = class extends BaseResource {
1494
1505
  this.createChunkGetRequestConfig(params)
1495
1506
  );
1496
1507
  }
1508
+ /**
1509
+ * Get a signed normalized source-file URL for compatibility and debugging.
1510
+ */
1511
+ async getPageCitationSource(documentId) {
1512
+ return this.httpClient.get(
1513
+ this.endpoint(`/documents/${documentId}/files/page-citation-source`)
1514
+ );
1515
+ }
1497
1516
  /**
1498
1517
  * Archive one canonical document by ID.
1499
1518
  */
@@ -1566,6 +1585,477 @@ var Documents = class extends BaseResource {
1566
1585
  // src/knowledge/knowledge.ts
1567
1586
  var import_crypto2 = require("crypto");
1568
1587
 
1588
+ // src/storage/asset-storage.ts
1589
+ var import_jszip2 = __toESM(require("jszip"));
1590
+ var pageCitationAssetsMetadataKey = "pageAssets";
1591
+ var defaultImageContentType = "application/octet-stream";
1592
+ var tableContentType = "text/html; charset=utf-8";
1593
+ async function storeParseResultAssets(result, options) {
1594
+ if (!options) {
1595
+ return {
1596
+ result,
1597
+ assetUrlsByFilePath: {}
1598
+ };
1599
+ }
1600
+ const normalizedKeyPrefix = normalizeStorageKeyPrefix(options.keyPrefix);
1601
+ const assets = dedupeStorageAssets(await collectStorageAssets(result, normalizedKeyPrefix));
1602
+ const assetUrlsByFilePath = {};
1603
+ for (const asset of assets) {
1604
+ const storedUrl = await storeAsset(asset, options.adapter, options.skipExisting ?? true);
1605
+ if (storedUrl) {
1606
+ assetUrlsByFilePath[asset.sourcePath] = storedUrl;
1607
+ }
1608
+ }
1609
+ const normalizedResult = Object.keys(assetUrlsByFilePath).length === 0 ? result : rewriteResultAssetUrls(result, assetUrlsByFilePath);
1610
+ return {
1611
+ result: normalizedResult,
1612
+ assetUrlsByFilePath
1613
+ };
1614
+ }
1615
+ async function collectStorageAssets(result, keyPrefix) {
1616
+ const resultZip = await loadResultZipForPageCitationAssets(result);
1617
+ return [
1618
+ ...result.imageChunks.flatMap((chunk) => collectImageStorageAsset(chunk, keyPrefix)),
1619
+ ...result.tableChunks.flatMap((chunk) => collectTableStorageAsset(chunk, keyPrefix)),
1620
+ ...resultZip ? await collectPageCitationStorageAssets(result.pageChunks, keyPrefix, resultZip) : []
1621
+ ];
1622
+ }
1623
+ function collectImageStorageAsset(chunk, keyPrefix) {
1624
+ const sourcePath = normalizeResultAssetPath(chunk.filePath);
1625
+ if (!sourcePath) return [];
1626
+ return [
1627
+ {
1628
+ sourcePath,
1629
+ storageKey: `${keyPrefix}/${sourcePath}`,
1630
+ body: chunk.data,
1631
+ contentType: getImageContentType(chunk.filePath),
1632
+ metadata: {
1633
+ chunkId: chunk.chunkId,
1634
+ chunkType: chunk.type,
1635
+ sourcePath
1636
+ }
1637
+ }
1638
+ ];
1639
+ }
1640
+ function collectTableStorageAsset(chunk, keyPrefix) {
1641
+ const sourcePath = normalizeResultAssetPath(chunk.filePath);
1642
+ if (!sourcePath) return [];
1643
+ return [
1644
+ {
1645
+ sourcePath,
1646
+ storageKey: `${keyPrefix}/${sourcePath}`,
1647
+ body: Buffer.from(chunk.html, "utf8"),
1648
+ contentType: tableContentType,
1649
+ metadata: {
1650
+ chunkId: chunk.chunkId,
1651
+ chunkType: chunk.type,
1652
+ sourcePath
1653
+ }
1654
+ }
1655
+ ];
1656
+ }
1657
+ async function collectPageCitationStorageAssets(chunks, keyPrefix, resultZip) {
1658
+ const assets = [];
1659
+ for (const chunk of chunks) {
1660
+ const pageAssets = parsePageCitationAssets(chunk.metadata[pageCitationAssetsMetadataKey]);
1661
+ for (const pageAsset of pageAssets) {
1662
+ const sourcePath = normalizeResultAssetPath(pageAsset.artifactRef);
1663
+ if (!sourcePath) continue;
1664
+ const zipEntry = resultZip.file(sourcePath);
1665
+ if (!zipEntry) continue;
1666
+ assets.push({
1667
+ sourcePath,
1668
+ storageKey: `${keyPrefix}/${sourcePath}`,
1669
+ body: await zipEntry.async("uint8array"),
1670
+ contentType: pageAsset.contentType,
1671
+ metadata: getPageCitationAssetMetadata(chunk, pageAsset, sourcePath)
1672
+ });
1673
+ }
1674
+ }
1675
+ return assets;
1676
+ }
1677
+ function getPageCitationAssetMetadata(chunk, pageAsset, sourcePath) {
1678
+ return {
1679
+ chunkId: chunk.chunkId,
1680
+ chunkType: chunk.type,
1681
+ pageNum: String(pageAsset.pageNum),
1682
+ sourcePath
1683
+ };
1684
+ }
1685
+ async function loadResultZipForPageCitationAssets(result) {
1686
+ if (!hasPageCitationAssetReferences(result) || result.rawZip.length === 0) {
1687
+ return null;
1688
+ }
1689
+ try {
1690
+ return await import_jszip2.default.loadAsync(result.rawZip);
1691
+ } catch (cause) {
1692
+ throw new Error("Unable to read page citation assets from the Knowhere result ZIP.", {
1693
+ cause
1694
+ });
1695
+ }
1696
+ }
1697
+ function hasPageCitationAssetReferences(result) {
1698
+ return result.pageChunks.some(
1699
+ (chunk) => parsePageCitationAssets(chunk.metadata[pageCitationAssetsMetadataKey]).length > 0
1700
+ );
1701
+ }
1702
+ function dedupeStorageAssets(assets) {
1703
+ const assetsBySourcePath = /* @__PURE__ */ new Map();
1704
+ for (const asset of assets) {
1705
+ if (!assetsBySourcePath.has(asset.sourcePath)) {
1706
+ assetsBySourcePath.set(asset.sourcePath, asset);
1707
+ }
1708
+ }
1709
+ return [...assetsBySourcePath.values()];
1710
+ }
1711
+ async function storeAsset(asset, adapter, skipExisting) {
1712
+ if (skipExisting && adapter.headObject) {
1713
+ const existing = await adapter.headObject(asset.storageKey);
1714
+ if (existing) {
1715
+ const existingUrl = existing.url ?? await adapter.getObjectUrl?.(asset.storageKey);
1716
+ if (existingUrl) return existingUrl;
1717
+ }
1718
+ }
1719
+ const writeResult = await adapter.writeObject({
1720
+ key: asset.storageKey,
1721
+ body: asset.body,
1722
+ contentType: asset.contentType,
1723
+ metadata: asset.metadata
1724
+ });
1725
+ return writeResult.url ?? await adapter.getObjectUrl?.(writeResult.key) ?? null;
1726
+ }
1727
+ function rewriteResultAssetUrls(result, assetUrlsByFilePath) {
1728
+ const chunks = result.chunks.map((chunk) => rewriteChunkAssetUrls(chunk, assetUrlsByFilePath));
1729
+ result.chunks.splice(0, result.chunks.length, ...chunks);
1730
+ refreshStaticChunkCollections(result);
1731
+ return result;
1732
+ }
1733
+ function rewriteChunkAssetUrls(chunk, assetUrlsByPath) {
1734
+ if (chunk.type === "image" || chunk.type === "table") {
1735
+ const sourcePath = normalizeResultAssetPath(chunk.filePath);
1736
+ const assetUrl = sourcePath ? assetUrlsByPath[sourcePath] : void 0;
1737
+ if (!assetUrl) return chunk;
1738
+ return {
1739
+ ...chunk,
1740
+ assetUrl
1741
+ };
1742
+ }
1743
+ if (chunk.type === "page") {
1744
+ const pageAssets = parsePageCitationAssets(chunk.metadata[pageCitationAssetsMetadataKey]);
1745
+ if (pageAssets.length === 0) return chunk;
1746
+ const rewrittenPageAssets = pageAssets.map((pageAsset) => {
1747
+ const sourcePath = normalizeResultAssetPath(pageAsset.artifactRef);
1748
+ const assetUrl = sourcePath ? assetUrlsByPath[sourcePath] : void 0;
1749
+ return assetUrl ? { ...pageAsset, assetUrl } : pageAsset;
1750
+ });
1751
+ return {
1752
+ ...chunk,
1753
+ metadata: {
1754
+ ...chunk.metadata,
1755
+ [pageCitationAssetsMetadataKey]: rewrittenPageAssets
1756
+ }
1757
+ };
1758
+ }
1759
+ return chunk;
1760
+ }
1761
+ function parsePageCitationAssets(value) {
1762
+ if (!Array.isArray(value)) return [];
1763
+ return value.flatMap((item) => {
1764
+ if (!isRecord(item)) return [];
1765
+ const pageNum = item.pageNum;
1766
+ const artifactRef = item.artifactRef;
1767
+ const contentType = item.contentType;
1768
+ const source = item.source;
1769
+ if (typeof pageNum !== "number" || !Number.isSafeInteger(pageNum) || pageNum <= 0) return [];
1770
+ if (typeof artifactRef !== "string") return [];
1771
+ if (contentType !== "image/png" && contentType !== "image/jpeg") return [];
1772
+ if (source !== "knowhere-rendered-page-citation-source") return [];
1773
+ return [
1774
+ {
1775
+ pageNum,
1776
+ artifactRef,
1777
+ assetUrl: typeof item.assetUrl === "string" ? item.assetUrl : void 0,
1778
+ contentType,
1779
+ width: typeof item.width === "number" ? item.width : void 0,
1780
+ height: typeof item.height === "number" ? item.height : void 0,
1781
+ source
1782
+ }
1783
+ ];
1784
+ });
1785
+ }
1786
+ function normalizeStorageKeyPrefix(value) {
1787
+ const normalized = normalizeStoragePath(value, { allowDotPrefix: false });
1788
+ if (!normalized) {
1789
+ throw new Error("storageAdapter keyPrefix must be a non-empty relative POSIX path.");
1790
+ }
1791
+ return normalized;
1792
+ }
1793
+ function normalizeResultAssetPath(value) {
1794
+ return normalizeStoragePath(value, { allowDotPrefix: true });
1795
+ }
1796
+ function normalizeStoragePath(value, options) {
1797
+ if (!value) return null;
1798
+ const trimmed = value.trim();
1799
+ if (trimmed.length === 0 || trimmed.includes("\0")) return null;
1800
+ if (trimmed.startsWith("/") || /^[A-Za-z]:/.test(trimmed)) return null;
1801
+ const normalized = options.allowDotPrefix ? trimmed.replaceAll("\\", "/").replace(/^\.\/+/, "") : trimmed.replaceAll("\\", "/");
1802
+ if (normalized.length === 0) return null;
1803
+ const parts = normalized.split("/");
1804
+ if (parts.some((part) => part.length === 0 || part === "." || part === "..")) return null;
1805
+ return parts.join("/");
1806
+ }
1807
+ function refreshStaticChunkCollections(result) {
1808
+ replaceStaticChunkCollection(
1809
+ result,
1810
+ "textChunks",
1811
+ result.chunks.filter((chunk) => chunk.type === "text")
1812
+ );
1813
+ replaceStaticChunkCollection(
1814
+ result,
1815
+ "imageChunks",
1816
+ result.chunks.filter((chunk) => chunk.type === "image")
1817
+ );
1818
+ replaceStaticChunkCollection(
1819
+ result,
1820
+ "tableChunks",
1821
+ result.chunks.filter((chunk) => chunk.type === "table")
1822
+ );
1823
+ replaceStaticChunkCollection(
1824
+ result,
1825
+ "pageChunks",
1826
+ result.chunks.filter((chunk) => chunk.type === "page")
1827
+ );
1828
+ }
1829
+ function replaceStaticChunkCollection(result, property, value) {
1830
+ const descriptor = Object.getOwnPropertyDescriptor(result, property);
1831
+ if (!descriptor || descriptor.get || descriptor.writable !== true) return;
1832
+ Object.defineProperty(result, property, {
1833
+ ...descriptor,
1834
+ value
1835
+ });
1836
+ }
1837
+ function getImageContentType(filePath) {
1838
+ const extension = filePath.split(".").at(-1)?.toLowerCase();
1839
+ if (extension === "jpg" || extension === "jpeg") return "image/jpeg";
1840
+ if (extension === "png") return "image/png";
1841
+ if (extension === "webp") return "image/webp";
1842
+ if (extension === "gif") return "image/gif";
1843
+ if (extension === "svg") return "image/svg+xml";
1844
+ return defaultImageContentType;
1845
+ }
1846
+ function isRecord(value) {
1847
+ return typeof value === "object" && value !== null;
1848
+ }
1849
+
1850
+ // src/storage/parsed-document-storage.ts
1851
+ var import_jszip3 = __toESM(require("jszip"));
1852
+ var parsedDocumentCommitPath = ".knowhere-sdk/commit.json";
1853
+ var jsonContentType = "application/json; charset=utf-8";
1854
+ var markdownContentType = "text/markdown; charset=utf-8";
1855
+ var csvContentType = "text/csv; charset=utf-8";
1856
+ var htmlContentType = "text/html; charset=utf-8";
1857
+ var binaryContentType = "application/octet-stream";
1858
+ async function syncParseResultToParsedDocumentStorage(params) {
1859
+ const documentId = params.documentId ?? params.result.documentId ?? params.result.jobId;
1860
+ const revisionKey = params.revisionKey ?? params.result.jobId;
1861
+ const objects = await collectResultLayoutObjects(params.result);
1862
+ for (const object of objects) {
1863
+ await params.storage.writeObject({
1864
+ documentId,
1865
+ revisionKey,
1866
+ ...object
1867
+ });
1868
+ }
1869
+ const commit = {
1870
+ version: 1,
1871
+ documentId,
1872
+ revisionKey,
1873
+ source: params.source ?? "resultZip",
1874
+ committedAt: (/* @__PURE__ */ new Date()).toISOString()
1875
+ };
1876
+ await writeParsedDocumentJsonObject(params.storage, {
1877
+ documentId,
1878
+ revisionKey,
1879
+ path: parsedDocumentCommitPath,
1880
+ value: commit
1881
+ });
1882
+ return {
1883
+ result: params.result,
1884
+ documentId,
1885
+ revisionKey,
1886
+ commit
1887
+ };
1888
+ }
1889
+ async function readParsedDocumentCommit(params) {
1890
+ const object = await params.storage.readObject({
1891
+ documentId: params.documentId,
1892
+ revisionKey: params.revisionKey,
1893
+ path: parsedDocumentCommitPath
1894
+ });
1895
+ if (!object) {
1896
+ return null;
1897
+ }
1898
+ const commit = readJsonObject(object);
1899
+ return commit.revisionKey === params.revisionKey && commit.documentId === params.documentId ? commit : null;
1900
+ }
1901
+ function readJsonObject(object) {
1902
+ return JSON.parse(Buffer.from(object.body).toString("utf8"));
1903
+ }
1904
+ async function collectResultLayoutObjects(result) {
1905
+ const objectsByPath = /* @__PURE__ */ new Map();
1906
+ const rawZipObjects = await collectRawZipObjects(result.rawZip);
1907
+ for (const object of rawZipObjects) {
1908
+ objectsByPath.set(object.path, object);
1909
+ }
1910
+ for (const object of collectGeneratedResultObjects(result)) {
1911
+ if (!objectsByPath.has(object.path)) {
1912
+ objectsByPath.set(object.path, object);
1913
+ }
1914
+ }
1915
+ return [...objectsByPath.values()].filter((object) => !isSdkMetadataPath(object.path));
1916
+ }
1917
+ async function collectRawZipObjects(rawZip) {
1918
+ try {
1919
+ const zip = await import_jszip3.default.loadAsync(rawZip);
1920
+ const objects = [];
1921
+ for (const entry of Object.values(zip.files)) {
1922
+ if (entry.dir || entry.name === "result.zip") {
1923
+ continue;
1924
+ }
1925
+ objects.push({
1926
+ path: normalizeResultPath(entry.name),
1927
+ body: new Uint8Array(await entry.async("nodebuffer")),
1928
+ contentType: inferContentTypeFromPath(entry.name)
1929
+ });
1930
+ }
1931
+ return objects;
1932
+ } catch {
1933
+ return [];
1934
+ }
1935
+ }
1936
+ function collectGeneratedResultObjects(result) {
1937
+ const objects = [
1938
+ createJsonObject("manifest.json", result.manifest),
1939
+ createJsonObject("chunks.json", keysToSnake(serializeChunks2(result.chunks)))
1940
+ ];
1941
+ if (result.docNav) {
1942
+ objects.push(createJsonObject("doc_nav.json", result.docNav));
1943
+ }
1944
+ if (result.fullMarkdown) {
1945
+ objects.push(createTextObject("full.md", result.fullMarkdown, markdownContentType));
1946
+ }
1947
+ if (result.chunksSlim) {
1948
+ objects.push(createJsonObject("chunks_slim.json", { chunks: result.chunksSlim }));
1949
+ }
1950
+ if (result.hierarchy) {
1951
+ objects.push(createJsonObject("hierarchy.json", result.hierarchy));
1952
+ }
1953
+ if (result.tocHierarchies) {
1954
+ objects.push(createJsonObject("toc_hierarchies.json", result.tocHierarchies));
1955
+ }
1956
+ if (result.kbCsv) {
1957
+ objects.push(createTextObject("kb.csv", result.kbCsv, csvContentType));
1958
+ }
1959
+ if (result.hierarchyViewHtml) {
1960
+ objects.push(
1961
+ createTextObject("hierarchy_view.html", result.hierarchyViewHtml, htmlContentType)
1962
+ );
1963
+ }
1964
+ for (const chunk of result.imageChunks) {
1965
+ objects.push({
1966
+ path: normalizeResultPath(chunk.filePath),
1967
+ body: new Uint8Array(chunk.data),
1968
+ contentType: inferContentTypeFromPath(chunk.filePath),
1969
+ metadata: {
1970
+ chunkId: chunk.chunkId,
1971
+ chunkType: chunk.type,
1972
+ sourcePath: chunk.filePath
1973
+ }
1974
+ });
1975
+ }
1976
+ for (const chunk of result.tableChunks) {
1977
+ objects.push({
1978
+ path: normalizeResultPath(chunk.filePath),
1979
+ body: Buffer.from(chunk.html, "utf8"),
1980
+ contentType: htmlContentType,
1981
+ metadata: {
1982
+ chunkId: chunk.chunkId,
1983
+ chunkType: chunk.type,
1984
+ sourcePath: chunk.filePath
1985
+ }
1986
+ });
1987
+ }
1988
+ return objects;
1989
+ }
1990
+ function serializeChunks2(chunks) {
1991
+ return {
1992
+ chunks: chunks.map((chunk) => {
1993
+ const rawChunk = {
1994
+ chunkId: chunk.chunkId,
1995
+ type: chunk.type,
1996
+ contentSource: chunk.contentSource,
1997
+ content: chunk.content,
1998
+ path: chunk.path,
1999
+ metadata: chunk.metadata
2000
+ };
2001
+ if (chunk.type === "image" || chunk.type === "table") {
2002
+ return {
2003
+ ...rawChunk,
2004
+ filePath: chunk.filePath,
2005
+ assetUrl: chunk.assetUrl
2006
+ };
2007
+ }
2008
+ return rawChunk;
2009
+ })
2010
+ };
2011
+ }
2012
+ async function writeParsedDocumentJsonObject(storage, params) {
2013
+ await storage.writeObject({
2014
+ documentId: params.documentId,
2015
+ revisionKey: params.revisionKey,
2016
+ path: params.path,
2017
+ body: Buffer.from(JSON.stringify(params.value, null, 2), "utf8"),
2018
+ contentType: jsonContentType
2019
+ });
2020
+ }
2021
+ function createJsonObject(path4, value) {
2022
+ return {
2023
+ path: path4,
2024
+ body: Buffer.from(JSON.stringify(value, null, 2), "utf8"),
2025
+ contentType: jsonContentType
2026
+ };
2027
+ }
2028
+ function createTextObject(path4, value, contentType) {
2029
+ return {
2030
+ path: path4,
2031
+ body: Buffer.from(value, "utf8"),
2032
+ contentType
2033
+ };
2034
+ }
2035
+ function inferContentTypeFromPath(filePath) {
2036
+ const lower = filePath.toLowerCase();
2037
+ if (lower.endsWith(".json")) return jsonContentType;
2038
+ if (lower.endsWith(".md")) return markdownContentType;
2039
+ if (lower.endsWith(".csv")) return csvContentType;
2040
+ if (lower.endsWith(".html") || lower.endsWith(".htm")) return htmlContentType;
2041
+ if (lower.endsWith(".png")) return "image/png";
2042
+ if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
2043
+ if (lower.endsWith(".gif")) return "image/gif";
2044
+ if (lower.endsWith(".webp")) return "image/webp";
2045
+ if (lower.endsWith(".svg")) return "image/svg+xml";
2046
+ return binaryContentType;
2047
+ }
2048
+ function normalizeResultPath(value) {
2049
+ const normalized = value.replaceAll("\\", "/").replace(/^\.\/+/, "");
2050
+ if (normalized.length === 0 || normalized.includes("\0") || normalized.startsWith("/") || normalized.split("/").some((part) => part.length === 0 || part === "." || part === "..")) {
2051
+ throw new Error(`Invalid parsed result path: ${value}`);
2052
+ }
2053
+ return normalized;
2054
+ }
2055
+ function isSdkMetadataPath(path4) {
2056
+ return path4 === ".knowhere-sdk" || path4.startsWith(".knowhere-sdk/");
2057
+ }
2058
+
1569
2059
  // src/knowledge/local-store.ts
1570
2060
  var import_crypto = require("crypto");
1571
2061
  var import_os = __toESM(require("os"));
@@ -1791,27 +2281,61 @@ function isMissingFileError2(error) {
1791
2281
  // src/knowledge/knowledge.ts
1792
2282
  var DEFAULT_READ_LIMIT = 12;
1793
2283
  var MAX_READ_LIMIT = 40;
2284
+ var DEFAULT_PAGE_SIZE = 20;
2285
+ var MAX_PAGE_SIZE = 200;
1794
2286
  var DEFAULT_GREP_LIMIT = 20;
1795
2287
  var MAX_GREP_LIMIT = 50;
1796
2288
  var DEFAULT_CONTEXT_CHARS = 80;
2289
+ var DEFAULT_REMOTE_SCAN_PAGE_SIZE = 100;
2290
+ var DEFAULT_MAX_SYNC_PAGES = 10;
2291
+ var DEFAULT_SYNC_DEADLINE_MS = 8e3;
2292
+ var DEFAULT_GREP_MAX_PAGES = 50;
2293
+ var DEFAULT_GREP_DEADLINE_MS = 8e3;
2294
+ var DEFAULT_OUTLINE_MAX_PAGES = 50;
2295
+ var DEFAULT_OUTLINE_DEADLINE_MS = 8e3;
1797
2296
  var SAFE_LOCAL_DOCUMENT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
1798
2297
  var REMOTE_DOCUMENT_ID_PATTERN = /^doc[_-]/;
2298
+ var remoteReconstructionPageDirectory = ".knowhere-sdk/remote-reconstruction/pages";
1799
2299
  var Knowledge = class _Knowledge {
1800
2300
  client;
1801
2301
  store;
2302
+ cacheDirectory;
2303
+ parsedStorageConfig;
1802
2304
  constructor(client, options) {
1803
2305
  this.client = client;
2306
+ this.cacheDirectory = options?.cacheDirectory;
2307
+ this.parsedStorageConfig = options?.parsedStorage;
1804
2308
  this.store = new LocalKnowledgeStore(options?.cacheDirectory);
1805
2309
  }
1806
2310
  withCacheDirectory(cacheDirectory) {
1807
- return new _Knowledge(this.client, { cacheDirectory });
2311
+ return new _Knowledge(this.client, {
2312
+ cacheDirectory,
2313
+ parsedStorage: this.parsedStorageConfig
2314
+ });
1808
2315
  }
1809
- async parse(params) {
1810
- const result = await this.client.parse(params);
2316
+ withParsedStorage(parsedStorage) {
2317
+ return new _Knowledge(this.client, {
2318
+ cacheDirectory: this.cacheDirectory,
2319
+ parsedStorage
2320
+ });
2321
+ }
2322
+ async parseToLocalCache(params) {
2323
+ const loadedResult = params.storageAdapter ? await this.parseWithoutClientStorageAdapter(params) : await this.client.parse(params);
2324
+ const storedAssets = await storeParseResultAssets(loadedResult, params.storageAdapter);
2325
+ const syncedStorage = await this.syncParseResultToConfiguredStorage(storedAssets.result);
2326
+ const result = syncedStorage.result;
1811
2327
  const document = await this.store.saveResult(result, {
1812
2328
  localDocumentId: params.localDocumentId
1813
2329
  });
1814
- return { document, result };
2330
+ return {
2331
+ document,
2332
+ result,
2333
+ assetUrlsByFilePath: storedAssets.assetUrlsByFilePath
2334
+ };
2335
+ }
2336
+ /** @deprecated Use parseToLocalCache for local cache imports or the top-level client.parse for server-safe parsing. */
2337
+ async parse(params) {
2338
+ return this.parseToLocalCache(params);
1815
2339
  }
1816
2340
  async startParse(params) {
1817
2341
  const job = await this.client.startParse(params);
@@ -1842,14 +2366,95 @@ var Knowledge = class _Knowledge {
1842
2366
  results
1843
2367
  };
1844
2368
  }
1845
- async cacheJobResult(params) {
1846
- const result = await this.client.jobs.load(params.jobId, {
2369
+ async loadJobResult(params) {
2370
+ const loadedResult = await this.client.jobs.load(params.jobId, {
1847
2371
  verifyChecksum: params.verifyChecksum
1848
2372
  });
2373
+ const storedAssets = await storeParseResultAssets(loadedResult, params.storageAdapter);
2374
+ const syncedStorage = await this.syncParseResultToConfiguredStorage(storedAssets.result);
2375
+ const result = syncedStorage.result;
2376
+ return {
2377
+ result,
2378
+ assetUrlsByFilePath: storedAssets.assetUrlsByFilePath
2379
+ };
2380
+ }
2381
+ async importJobResult(params) {
2382
+ const stored = await this.loadJobResult(params);
2383
+ const result = stored.result;
1849
2384
  const document = await this.store.saveResult(result, {
1850
2385
  localDocumentId: params.localDocumentId
1851
2386
  });
1852
- return { document, result };
2387
+ return {
2388
+ document,
2389
+ result,
2390
+ assetUrlsByFilePath: stored.assetUrlsByFilePath
2391
+ };
2392
+ }
2393
+ /** @deprecated Use importJobResult for local cache imports or loadJobResult for server-safe loads. */
2394
+ async cacheJobResult(params) {
2395
+ return this.importJobResult(params);
2396
+ }
2397
+ async syncParsedDocument(params) {
2398
+ const normalized = normalizeDocumentReference(params);
2399
+ const parsedStorageConfig = this.requireParsedStorageConfig();
2400
+ if (normalized.documentId) {
2401
+ return this.syncRemoteDocumentToParsedStorage({
2402
+ documentId: normalized.documentId,
2403
+ revisionKey: params.revisionKey,
2404
+ parsedStorageConfig
2405
+ });
2406
+ }
2407
+ if (normalized.jobId) {
2408
+ const loadedResult = await this.client.jobs.load(normalized.jobId);
2409
+ const synced2 = await syncParseResultToParsedDocumentStorage({
2410
+ result: loadedResult,
2411
+ storage: parsedStorageConfig.storage,
2412
+ documentId: loadedResult.documentId ?? normalized.localDocumentId ?? normalized.jobId,
2413
+ revisionKey: params.revisionKey ?? normalized.jobId
2414
+ });
2415
+ return {
2416
+ documentId: synced2.documentId,
2417
+ revisionKey: synced2.revisionKey,
2418
+ completed: true,
2419
+ commit: synced2.commit
2420
+ };
2421
+ }
2422
+ if (!normalized.localDocumentId) {
2423
+ throw new ValidationError("localDocumentId, documentId, or jobId is required");
2424
+ }
2425
+ const { result } = await this.store.loadResult(normalized.localDocumentId);
2426
+ const synced = await syncParseResultToParsedDocumentStorage({
2427
+ result,
2428
+ storage: parsedStorageConfig.storage,
2429
+ documentId: result.documentId ?? normalized.localDocumentId,
2430
+ revisionKey: params.revisionKey ?? result.jobId
2431
+ });
2432
+ return {
2433
+ documentId: synced.documentId,
2434
+ revisionKey: synced.revisionKey,
2435
+ completed: true,
2436
+ commit: synced.commit
2437
+ };
2438
+ }
2439
+ async syncParseResultToConfiguredStorage(result) {
2440
+ const parsedStorageConfig = this.parsedStorageConfig;
2441
+ if (!parsedStorageConfig) {
2442
+ return { result };
2443
+ }
2444
+ const synced = await syncParseResultToParsedDocumentStorage({
2445
+ result,
2446
+ storage: parsedStorageConfig.storage
2447
+ });
2448
+ return {
2449
+ result: synced.result,
2450
+ commit: synced.commit
2451
+ };
2452
+ }
2453
+ async parseWithoutClientStorageAdapter(params) {
2454
+ const parseParams = { ...params };
2455
+ delete parseParams.localDocumentId;
2456
+ delete parseParams.storageAdapter;
2457
+ return this.client.parse(parseParams);
1853
2458
  }
1854
2459
  async cacheDocument(params) {
1855
2460
  if (!params.documentId) {
@@ -1859,7 +2464,7 @@ var Knowledge = class _Knowledge {
1859
2464
  params.documentId
1860
2465
  );
1861
2466
  const jobId = await this.getPublishedDocumentJobId(params.documentId);
1862
- return this.cacheJobResult({
2467
+ return this.importJobResult({
1863
2468
  jobId,
1864
2469
  localDocumentId: params.localDocumentId ?? existing?.localDocumentId ?? createLocalDocumentIdForRemote(params.documentId)
1865
2470
  });
@@ -1896,7 +2501,7 @@ var Knowledge = class _Knowledge {
1896
2501
  };
1897
2502
  }
1898
2503
  try {
1899
- const cached = await this.cacheJobResult({
2504
+ const cached = await this.importJobResult({
1900
2505
  jobId,
1901
2506
  localDocumentId: trackedJob.localDocumentId
1902
2507
  });
@@ -1921,6 +2526,13 @@ var Knowledge = class _Knowledge {
1921
2526
  return this.store.listDocuments();
1922
2527
  }
1923
2528
  async getDocumentOutline(reference) {
2529
+ const normalized = normalizeDocumentReference(reference);
2530
+ if (normalized.documentId) {
2531
+ return this.getRemoteDocumentOutline(normalized.documentId, normalized.revisionKey);
2532
+ }
2533
+ if (normalized.localDocumentId && looksLikeRemoteDocumentId(normalized.localDocumentId) && !await this.store.getDocument(normalized.localDocumentId)) {
2534
+ return this.getRemoteDocumentOutline(normalized.localDocumentId, normalized.revisionKey);
2535
+ }
1924
2536
  const { document, result } = await this.loadReadableResult(reference);
1925
2537
  const chunks = indexChunks(result);
1926
2538
  const sections = buildFlatSections(result, chunks);
@@ -1936,6 +2548,14 @@ var Knowledge = class _Knowledge {
1936
2548
  };
1937
2549
  }
1938
2550
  async readChunks(params) {
2551
+ validateReadParams(params);
2552
+ const normalized = normalizeDocumentReference(params);
2553
+ if (normalized.documentId) {
2554
+ return this.readRemoteDocumentChunks(normalized.documentId, params);
2555
+ }
2556
+ if (normalized.localDocumentId && looksLikeRemoteDocumentId(normalized.localDocumentId) && !await this.store.getDocument(normalized.localDocumentId)) {
2557
+ return this.readRemoteDocumentChunks(normalized.localDocumentId, params);
2558
+ }
1939
2559
  const { document, result } = await this.loadReadableResult(params);
1940
2560
  const limit = clampLimit(params.limit, DEFAULT_READ_LIMIT, MAX_READ_LIMIT);
1941
2561
  const chunks = indexChunks(result).filter((chunk) => matchesReadScope(chunk, params));
@@ -1952,6 +2572,13 @@ var Knowledge = class _Knowledge {
1952
2572
  if (!params.pattern) {
1953
2573
  throw new ValidationError("pattern is required");
1954
2574
  }
2575
+ const normalized = normalizeDocumentReference(params);
2576
+ if (normalized.documentId) {
2577
+ return this.grepRemoteDocumentChunks(normalized.documentId, params);
2578
+ }
2579
+ if (normalized.localDocumentId && looksLikeRemoteDocumentId(normalized.localDocumentId) && !await this.store.getDocument(normalized.localDocumentId)) {
2580
+ return this.grepRemoteDocumentChunks(normalized.localDocumentId, params);
2581
+ }
1955
2582
  const { document, result } = await this.loadReadableResult(params);
1956
2583
  const maxResults = clampLimit(params.maxResults, DEFAULT_GREP_LIMIT, MAX_GREP_LIMIT);
1957
2584
  const contextChars = params.contextChars ?? DEFAULT_CONTEXT_CHARS;
@@ -2031,7 +2658,7 @@ var Knowledge = class _Knowledge {
2031
2658
  });
2032
2659
  }
2033
2660
  if (normalized.jobId) {
2034
- return this.cacheJobResult({
2661
+ return this.importJobResult({
2035
2662
  jobId: normalized.jobId,
2036
2663
  localDocumentId: normalized.localDocumentId
2037
2664
  });
@@ -2051,21 +2678,657 @@ var Knowledge = class _Knowledge {
2051
2678
  }
2052
2679
  throw new Error(`Local Knowhere document not found: ${normalized.localDocumentId}`);
2053
2680
  }
2054
- async findLocalDocumentByRemoteId(documentId) {
2055
- const documents = await this.store.listDocuments();
2056
- return documents.find((document) => document.documentId === documentId);
2681
+ async readRemoteDocumentChunks(documentId, params) {
2682
+ const explicitRevisionKey = params.revisionKey ?? params.jobId;
2683
+ if (explicitRevisionKey) {
2684
+ const stored2 = await this.tryReadChunksFromParsedStorage({
2685
+ documentId,
2686
+ revisionKey: explicitRevisionKey,
2687
+ params
2688
+ });
2689
+ if (stored2) {
2690
+ return stored2;
2691
+ }
2692
+ }
2693
+ if (hasScanReadFilters(params)) {
2694
+ return this.scanRemoteDocumentChunks(documentId, params);
2695
+ }
2696
+ const pageParams = getReadPageParams(params);
2697
+ const remotePage = await this.client.documents.listChunks(documentId, {
2698
+ page: pageParams.page,
2699
+ pageSize: pageParams.pageSize,
2700
+ chunkType: params.chunkType,
2701
+ includeAssetUrls: true
2702
+ });
2703
+ const revisionKey = getRemoteRevisionKey(remotePage);
2704
+ const stored = await this.tryReadChunksFromParsedStorage({
2705
+ documentId,
2706
+ revisionKey,
2707
+ params
2708
+ });
2709
+ if (stored) {
2710
+ return stored;
2711
+ }
2712
+ const chunks = remotePage.chunks.map((chunk) => toRemoteReadChunk(chunk));
2713
+ this.scheduleParsedStorageSync(documentId, revisionKey);
2714
+ return {
2715
+ document: createRemoteKnowledgeDocument({
2716
+ documentId,
2717
+ response: remotePage,
2718
+ revisionKey
2719
+ }),
2720
+ chunks,
2721
+ page: remotePage.pagination.page,
2722
+ pageSize: remotePage.pagination.pageSize,
2723
+ totalChunks: remotePage.pagination.total,
2724
+ totalPages: remotePage.pagination.totalPages
2725
+ };
2057
2726
  }
2058
- async getPublishedDocumentJobId(documentId) {
2059
- const response = await this.client.documents.listChunks(documentId, {
2727
+ async scanRemoteDocumentChunks(documentId, params) {
2728
+ const explicitRevisionKey = params.revisionKey ?? params.jobId;
2729
+ if (explicitRevisionKey) {
2730
+ const stored2 = await this.tryReadChunksFromParsedStorage({
2731
+ documentId,
2732
+ revisionKey: explicitRevisionKey,
2733
+ params
2734
+ });
2735
+ if (stored2) {
2736
+ return stored2;
2737
+ }
2738
+ }
2739
+ const limit = clampLimit(params.limit, DEFAULT_READ_LIMIT, MAX_READ_LIMIT);
2740
+ const limits = getParsedStorageLimits(this.parsedStorageConfig?.limits);
2741
+ const firstPage = await this.client.documents.listChunks(documentId, {
2060
2742
  page: 1,
2061
- pageSize: 1
2743
+ pageSize: limits.remotePageSize,
2744
+ chunkType: params.chunkType,
2745
+ includeAssetUrls: true
2062
2746
  });
2063
- if (!response.jobId) {
2064
- throw new Error(
2065
- `Cannot sync server document ${documentId}: current published job id was not returned.`
2066
- );
2747
+ const revisionKey = getRemoteRevisionKey(firstPage);
2748
+ const stored = await this.tryReadChunksFromParsedStorage({
2749
+ documentId,
2750
+ revisionKey,
2751
+ params
2752
+ });
2753
+ if (stored) {
2754
+ return stored;
2067
2755
  }
2068
- return response.jobId;
2756
+ const selected = [];
2757
+ let scannedPages = 0;
2758
+ let page = 1;
2759
+ let response = firstPage;
2760
+ const startedAt = Date.now();
2761
+ while (true) {
2762
+ scannedPages += 1;
2763
+ selected.push(...response.chunks.filter((chunk) => matchesRemoteReadScope(chunk, params)));
2764
+ if (selected.length >= limit || response.pagination.page >= response.pagination.totalPages || scannedPages >= limits.outlineMaxPages || Date.now() - startedAt >= limits.outlineDeadlineMs) {
2765
+ break;
2766
+ }
2767
+ page += 1;
2768
+ response = await this.client.documents.listChunks(documentId, {
2769
+ page,
2770
+ pageSize: limits.remotePageSize,
2771
+ chunkType: params.chunkType,
2772
+ includeAssetUrls: true
2773
+ });
2774
+ }
2775
+ const chunks = selected.slice(0, limit).map((chunk) => toRemoteReadChunk(chunk));
2776
+ this.scheduleParsedStorageSync(documentId, revisionKey);
2777
+ return {
2778
+ document: createRemoteKnowledgeDocument({
2779
+ documentId,
2780
+ response: firstPage,
2781
+ revisionKey
2782
+ }),
2783
+ chunks,
2784
+ nextChunk: selected.length > limit ? toChunkPosition(selected[limit]) : void 0,
2785
+ totalChunks: firstPage.pagination.total,
2786
+ totalPages: firstPage.pagination.totalPages
2787
+ };
2788
+ }
2789
+ async grepRemoteDocumentChunks(documentId, params) {
2790
+ const explicitRevisionKey = params.revisionKey ?? params.jobId;
2791
+ if (explicitRevisionKey && !params.continuationCursor) {
2792
+ const stored2 = await this.tryGrepChunksFromParsedStorage({
2793
+ documentId,
2794
+ revisionKey: explicitRevisionKey,
2795
+ params
2796
+ });
2797
+ if (stored2) {
2798
+ return stored2;
2799
+ }
2800
+ }
2801
+ const maxResults = clampLimit(params.maxResults, DEFAULT_GREP_LIMIT, MAX_GREP_LIMIT);
2802
+ const contextChars = params.contextChars ?? DEFAULT_CONTEXT_CHARS;
2803
+ const matcher = createMatcher(params);
2804
+ const limits = getParsedStorageLimits(this.parsedStorageConfig?.limits);
2805
+ const cursor = parseContinuationCursor(params.continuationCursor);
2806
+ const startPage = cursor?.documentId === documentId ? cursor.nextPage : 1;
2807
+ const firstPage = await this.client.documents.listChunks(documentId, {
2808
+ page: startPage,
2809
+ pageSize: limits.remotePageSize,
2810
+ chunkType: params.chunkType,
2811
+ includeAssetUrls: true
2812
+ });
2813
+ const revisionKey = getRemoteRevisionKey(firstPage);
2814
+ const stored = startPage === 1 ? await this.tryGrepChunksFromParsedStorage({
2815
+ documentId,
2816
+ revisionKey,
2817
+ params
2818
+ }) : null;
2819
+ if (stored) {
2820
+ return stored;
2821
+ }
2822
+ const matches = [];
2823
+ let scannedChunks = 0;
2824
+ let scannedPages = 0;
2825
+ let response = firstPage;
2826
+ let page = startPage;
2827
+ const startedAt = Date.now();
2828
+ while (true) {
2829
+ scannedPages += 1;
2830
+ for (const [chunkIndex, chunk] of response.chunks.entries()) {
2831
+ if (response.pagination.page === startPage && cursor?.nextChunkIndex !== void 0 && chunkIndex < cursor.nextChunkIndex) {
2832
+ continue;
2833
+ }
2834
+ if (!matchesGrepScope(toIndexedRemoteChunk(chunk), params)) {
2835
+ continue;
2836
+ }
2837
+ scannedChunks += 1;
2838
+ const chunkMatches = matcher(chunk.content ?? "");
2839
+ for (const [matchIndex, match] of chunkMatches.entries()) {
2840
+ if (response.pagination.page === startPage && cursor?.nextChunkIndex === chunkIndex && cursor.nextMatchIndex !== void 0 && matchIndex < cursor.nextMatchIndex) {
2841
+ continue;
2842
+ }
2843
+ matches.push({
2844
+ position: toChunkPosition(chunk),
2845
+ chunkId: chunk.chunkId,
2846
+ chunkType: chunk.chunkType,
2847
+ sectionPath: chunk.sectionPath ?? "",
2848
+ sourceChunkPath: chunk.sourceChunkPath ?? chunk.sectionPath ?? "",
2849
+ filePath: chunk.filePath ?? void 0,
2850
+ startOffset: match.startOffset,
2851
+ endOffset: match.endOffset,
2852
+ snippet: buildSnippet(
2853
+ chunk.content ?? "",
2854
+ match.startOffset,
2855
+ match.endOffset,
2856
+ contextChars
2857
+ )
2858
+ });
2859
+ if (matches.length >= maxResults) {
2860
+ const continuationCursor = createGrepContinuationCursor({
2861
+ documentId,
2862
+ revisionKey,
2863
+ response,
2864
+ chunkIndex,
2865
+ matchIndex,
2866
+ chunkMatchCount: chunkMatches.length
2867
+ });
2868
+ this.scheduleParsedStorageSync(documentId, revisionKey);
2869
+ return {
2870
+ document: createRemoteKnowledgeDocument({
2871
+ documentId,
2872
+ response: firstPage,
2873
+ revisionKey
2874
+ }),
2875
+ matches,
2876
+ scannedChunks,
2877
+ truncated: continuationCursor !== void 0,
2878
+ continuationCursor
2879
+ };
2880
+ }
2881
+ }
2882
+ }
2883
+ const shouldStop = response.pagination.page >= response.pagination.totalPages || scannedPages >= limits.grepMaxPages || Date.now() - startedAt >= limits.grepDeadlineMs;
2884
+ if (shouldStop) {
2885
+ const truncated = response.pagination.page < response.pagination.totalPages;
2886
+ this.scheduleParsedStorageSync(documentId, revisionKey);
2887
+ return {
2888
+ document: createRemoteKnowledgeDocument({
2889
+ documentId,
2890
+ response: firstPage,
2891
+ revisionKey
2892
+ }),
2893
+ matches,
2894
+ scannedChunks,
2895
+ truncated,
2896
+ continuationCursor: truncated ? createContinuationCursor({
2897
+ documentId,
2898
+ revisionKey,
2899
+ nextPage: response.pagination.page + 1
2900
+ }) : void 0
2901
+ };
2902
+ }
2903
+ page += 1;
2904
+ response = await this.client.documents.listChunks(documentId, {
2905
+ page,
2906
+ pageSize: limits.remotePageSize,
2907
+ chunkType: params.chunkType,
2908
+ includeAssetUrls: true
2909
+ });
2910
+ }
2911
+ }
2912
+ async getRemoteDocumentOutline(documentId, revisionKeyOverride) {
2913
+ if (revisionKeyOverride) {
2914
+ const stored2 = await this.tryReadOutlineFromParsedStorage(documentId, revisionKeyOverride);
2915
+ if (stored2) {
2916
+ return stored2;
2917
+ }
2918
+ }
2919
+ const limits = getParsedStorageLimits(this.parsedStorageConfig?.limits);
2920
+ const firstPage = await this.client.documents.listChunks(documentId, {
2921
+ page: 1,
2922
+ pageSize: limits.remotePageSize,
2923
+ includeAssetUrls: true
2924
+ });
2925
+ const revisionKey = getRemoteRevisionKey(firstPage);
2926
+ const stored = await this.tryReadOutlineFromParsedStorage(documentId, revisionKey);
2927
+ if (stored) {
2928
+ return stored;
2929
+ }
2930
+ const chunks = [];
2931
+ let page = 1;
2932
+ let response = firstPage;
2933
+ let scannedPages = 0;
2934
+ const startedAt = Date.now();
2935
+ while (true) {
2936
+ scannedPages += 1;
2937
+ chunks.push(...response.chunks.map(toIndexedRemoteChunk));
2938
+ if (response.pagination.page >= response.pagination.totalPages || scannedPages >= limits.outlineMaxPages || Date.now() - startedAt >= limits.outlineDeadlineMs) {
2939
+ break;
2940
+ }
2941
+ page += 1;
2942
+ response = await this.client.documents.listChunks(documentId, {
2943
+ page,
2944
+ pageSize: limits.remotePageSize,
2945
+ includeAssetUrls: true
2946
+ });
2947
+ }
2948
+ const truncated = response.pagination.page < response.pagination.totalPages;
2949
+ const sections = buildFlatSectionsFromIndexedChunks(chunks);
2950
+ this.scheduleParsedStorageSync(documentId, revisionKey);
2951
+ return {
2952
+ document: createRemoteKnowledgeDocument({
2953
+ documentId,
2954
+ response: firstPage,
2955
+ revisionKey,
2956
+ typeCounts: countIndexedTypes(chunks)
2957
+ }),
2958
+ totalChunks: firstPage.pagination.total,
2959
+ typeCounts: countIndexedTypes(chunks),
2960
+ sections,
2961
+ sectionTree: nestSections(sections),
2962
+ truncated,
2963
+ continuationCursor: truncated ? createContinuationCursor({
2964
+ documentId,
2965
+ revisionKey,
2966
+ nextPage: response.pagination.page + 1
2967
+ }) : void 0
2968
+ };
2969
+ }
2970
+ async tryReadChunksFromParsedStorage(params) {
2971
+ const storedResult = await this.readCommittedParsedStorageResult(
2972
+ params.documentId,
2973
+ params.revisionKey
2974
+ );
2975
+ if (!storedResult) return null;
2976
+ const pageParams = getReadPageParams(params.params);
2977
+ const scopedChunks = storedResult.chunks.filter((chunk) => !params.params.chunkType || chunk.chunkType === params.params.chunkType).filter((chunk) => matchesReadScope(chunk, params.params));
2978
+ const selected = hasScanReadFilters(params.params) ? selectReadWindow(
2979
+ scopedChunks,
2980
+ params.params,
2981
+ clampLimit(params.params.limit, DEFAULT_READ_LIMIT, MAX_READ_LIMIT)
2982
+ ) : scopedChunks.slice(
2983
+ (pageParams.page - 1) * pageParams.pageSize,
2984
+ pageParams.page * pageParams.pageSize
2985
+ );
2986
+ const visibleChunks = await applyStoredAssetUrlsToReadChunks({
2987
+ storageConfig: this.requireParsedStorageConfig(),
2988
+ documentId: params.documentId,
2989
+ revisionKey: params.revisionKey,
2990
+ chunks: selected.map(toReadChunk)
2991
+ });
2992
+ const totalChunks = scopedChunks.length;
2993
+ return {
2994
+ document: createStoredKnowledgeDocument({
2995
+ documentId: params.documentId,
2996
+ storedResult,
2997
+ revisionKey: params.revisionKey
2998
+ }),
2999
+ chunks: visibleChunks,
3000
+ page: hasScanReadFilters(params.params) ? void 0 : pageParams.page,
3001
+ pageSize: hasScanReadFilters(params.params) ? void 0 : pageParams.pageSize,
3002
+ totalChunks,
3003
+ totalPages: Math.max(1, Math.ceil(totalChunks / pageParams.pageSize)),
3004
+ nextChunk: getNextChunkPosition(scopedChunks, selected)
3005
+ };
3006
+ }
3007
+ async tryGrepChunksFromParsedStorage(params) {
3008
+ const storedResult = await this.readCommittedParsedStorageResult(
3009
+ params.documentId,
3010
+ params.revisionKey
3011
+ );
3012
+ if (!storedResult) return null;
3013
+ const maxResults = clampLimit(params.params.maxResults, DEFAULT_GREP_LIMIT, MAX_GREP_LIMIT);
3014
+ const contextChars = params.params.contextChars ?? DEFAULT_CONTEXT_CHARS;
3015
+ const matcher = createMatcher(params.params);
3016
+ const scopedChunks = storedResult.chunks.filter((chunk) => !params.params.chunkType || chunk.chunkType === params.params.chunkType).filter((chunk) => matchesGrepScope(chunk, params.params));
3017
+ const matches = [];
3018
+ let scannedChunks = 0;
3019
+ for (const chunk of scopedChunks) {
3020
+ scannedChunks += 1;
3021
+ for (const match of matcher(chunk.content)) {
3022
+ matches.push({
3023
+ position: chunk.position,
3024
+ chunkId: chunk.chunkId,
3025
+ chunkType: chunk.chunkType,
3026
+ sectionPath: chunk.sectionPath,
3027
+ sourceChunkPath: chunk.sourceChunkPath,
3028
+ filePath: chunk.filePath,
3029
+ startOffset: match.startOffset,
3030
+ endOffset: match.endOffset,
3031
+ snippet: buildSnippet(chunk.content, match.startOffset, match.endOffset, contextChars)
3032
+ });
3033
+ if (matches.length >= maxResults) {
3034
+ return {
3035
+ document: createStoredKnowledgeDocument({
3036
+ documentId: params.documentId,
3037
+ storedResult,
3038
+ revisionKey: params.revisionKey
3039
+ }),
3040
+ matches,
3041
+ scannedChunks,
3042
+ truncated: true
3043
+ };
3044
+ }
3045
+ }
3046
+ }
3047
+ return {
3048
+ document: createStoredKnowledgeDocument({
3049
+ documentId: params.documentId,
3050
+ storedResult,
3051
+ revisionKey: params.revisionKey
3052
+ }),
3053
+ matches,
3054
+ scannedChunks,
3055
+ truncated: false
3056
+ };
3057
+ }
3058
+ async tryReadOutlineFromParsedStorage(documentId, revisionKey) {
3059
+ const storedResult = await this.readCommittedParsedStorageResult(documentId, revisionKey);
3060
+ if (!storedResult) return null;
3061
+ const sections = storedResult.docNav?.sections && storedResult.docNav.sections.length > 0 ? flattenSections(
3062
+ storedResult.docNav.sections.map(
3063
+ (section) => toKnowledgeSection(
3064
+ section,
3065
+ storedResult.chunks,
3066
+ storedResult.manifest.sourceFileName
3067
+ )
3068
+ )
3069
+ ) : buildFlatSectionsFromIndexedChunks(storedResult.chunks);
3070
+ return {
3071
+ document: createStoredKnowledgeDocument({
3072
+ documentId,
3073
+ storedResult,
3074
+ revisionKey
3075
+ }),
3076
+ totalChunks: storedResult.chunks.length,
3077
+ typeCounts: storedResult.typeCounts,
3078
+ sections,
3079
+ sectionTree: storedResult.docNav?.sections && storedResult.docNav.sections.length > 0 ? storedResult.docNav.sections.map(
3080
+ (section) => toKnowledgeSection(
3081
+ section,
3082
+ storedResult.chunks,
3083
+ storedResult.manifest.sourceFileName
3084
+ )
3085
+ ) : nestSections(sections),
3086
+ truncated: false
3087
+ };
3088
+ }
3089
+ async readCommittedParsedStorageResult(documentId, revisionKey) {
3090
+ const parsedStorageConfig = this.parsedStorageConfig;
3091
+ if (!parsedStorageConfig) {
3092
+ return null;
3093
+ }
3094
+ const commit = await readParsedDocumentCommit({
3095
+ storage: parsedStorageConfig.storage,
3096
+ documentId,
3097
+ revisionKey
3098
+ });
3099
+ if (!commit) {
3100
+ return null;
3101
+ }
3102
+ const manifestObject = await parsedStorageConfig.storage.readObject({
3103
+ documentId,
3104
+ revisionKey,
3105
+ path: "manifest.json"
3106
+ });
3107
+ const chunksObject = await parsedStorageConfig.storage.readObject({
3108
+ documentId,
3109
+ revisionKey,
3110
+ path: "chunks.json"
3111
+ });
3112
+ if (!manifestObject || !chunksObject) {
3113
+ return null;
3114
+ }
3115
+ const manifest = readJsonObject(manifestObject);
3116
+ const chunks = parseStoredResultChunks({
3117
+ payload: readJsonObject(chunksObject),
3118
+ sourceFileName: manifest.sourceFileName
3119
+ });
3120
+ const docNavObject = await parsedStorageConfig.storage.readObject({
3121
+ documentId,
3122
+ revisionKey,
3123
+ path: "doc_nav.json"
3124
+ });
3125
+ const docNav = docNavObject ? readJsonObject(docNavObject) : void 0;
3126
+ return {
3127
+ commit,
3128
+ manifest,
3129
+ chunks,
3130
+ docNav: docNav?.sections ? { sections: docNav.sections } : void 0,
3131
+ typeCounts: countIndexedTypes(chunks)
3132
+ };
3133
+ }
3134
+ scheduleParsedStorageSync(documentId, revisionKey) {
3135
+ const parsedStorageConfig = this.parsedStorageConfig;
3136
+ if (!parsedStorageConfig) {
3137
+ return;
3138
+ }
3139
+ const scheduler = parsedStorageConfig.scheduler ?? defaultParsedStorageScheduler;
3140
+ const task = async () => {
3141
+ try {
3142
+ await this.syncRemoteDocumentToParsedStorage({
3143
+ documentId,
3144
+ revisionKey,
3145
+ parsedStorageConfig
3146
+ });
3147
+ } catch (error) {
3148
+ await parsedStorageConfig.storage.writeSyncProgress({
3149
+ documentId,
3150
+ revisionKey,
3151
+ nextChunkPage: 1,
3152
+ status: "failed",
3153
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3154
+ error: error instanceof Error ? error.message : String(error)
3155
+ });
3156
+ }
3157
+ };
3158
+ void Promise.resolve(scheduler.schedule(task)).catch(() => void 0);
3159
+ }
3160
+ async syncRemoteDocumentToParsedStorage(params) {
3161
+ const firstPage = await this.client.documents.listChunks(params.documentId, {
3162
+ page: 1,
3163
+ pageSize: 1,
3164
+ includeAssetUrls: true
3165
+ });
3166
+ const revisionKey = params.revisionKey ?? getRemoteRevisionKey(firstPage);
3167
+ if (params.revisionKey && getRemoteRevisionKey(firstPage) !== params.revisionKey) {
3168
+ return this.syncRemoteDocumentToParsedStorage({
3169
+ documentId: params.documentId,
3170
+ parsedStorageConfig: params.parsedStorageConfig
3171
+ });
3172
+ }
3173
+ if (firstPage.jobId) {
3174
+ try {
3175
+ const loadedResult = await this.client.jobs.load(firstPage.jobId);
3176
+ const synced = await syncParseResultToParsedDocumentStorage({
3177
+ result: loadedResult,
3178
+ storage: params.parsedStorageConfig.storage,
3179
+ documentId: params.documentId,
3180
+ revisionKey,
3181
+ source: "resultZip"
3182
+ });
3183
+ await params.parsedStorageConfig.storage.writeSyncProgress({
3184
+ documentId: params.documentId,
3185
+ revisionKey,
3186
+ status: "completed",
3187
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3188
+ });
3189
+ return {
3190
+ documentId: synced.documentId,
3191
+ revisionKey: synced.revisionKey,
3192
+ completed: true,
3193
+ commit: synced.commit
3194
+ };
3195
+ } catch {
3196
+ }
3197
+ }
3198
+ return this.syncRemoteDocumentChunksToParsedStorage({
3199
+ documentId: params.documentId,
3200
+ revisionKey,
3201
+ firstPage,
3202
+ parsedStorageConfig: params.parsedStorageConfig
3203
+ });
3204
+ }
3205
+ async syncRemoteDocumentChunksToParsedStorage(params) {
3206
+ const limits = getParsedStorageLimits(params.parsedStorageConfig.limits);
3207
+ const existingProgress = await params.parsedStorageConfig.storage.readSyncProgress({
3208
+ documentId: params.documentId,
3209
+ revisionKey: params.revisionKey
3210
+ });
3211
+ let nextPage = existingProgress?.status === "running" ? existingProgress.nextChunkPage ?? 1 : 1;
3212
+ let response = nextPage === 1 && params.firstPage.pagination.pageSize === limits.remotePageSize ? params.firstPage : await this.client.documents.listChunks(params.documentId, {
3213
+ page: nextPage,
3214
+ pageSize: limits.remotePageSize,
3215
+ includeAssetUrls: true
3216
+ });
3217
+ const totalPages = response.pagination.totalPages;
3218
+ let syncedPages = 0;
3219
+ const startedAt = Date.now();
3220
+ while (nextPage <= totalPages) {
3221
+ if (syncedPages > 0) {
3222
+ response = await this.client.documents.listChunks(params.documentId, {
3223
+ page: nextPage,
3224
+ pageSize: limits.remotePageSize,
3225
+ includeAssetUrls: true
3226
+ });
3227
+ }
3228
+ await params.parsedStorageConfig.storage.writeObject({
3229
+ documentId: params.documentId,
3230
+ revisionKey: params.revisionKey,
3231
+ path: getRemoteReconstructionPagePath(response.pagination.page),
3232
+ body: Buffer.from(JSON.stringify({ chunks: response.chunks }), "utf8"),
3233
+ contentType: "application/json; charset=utf-8"
3234
+ });
3235
+ nextPage += 1;
3236
+ syncedPages += 1;
3237
+ await params.parsedStorageConfig.storage.writeSyncProgress({
3238
+ documentId: params.documentId,
3239
+ revisionKey: params.revisionKey,
3240
+ nextChunkPage: nextPage,
3241
+ status: "running",
3242
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3243
+ });
3244
+ if (syncedPages >= limits.maxPagesPerSync || Date.now() - startedAt >= limits.syncDeadlineMs) {
3245
+ return {
3246
+ documentId: params.documentId,
3247
+ revisionKey: params.revisionKey,
3248
+ completed: false
3249
+ };
3250
+ }
3251
+ }
3252
+ const chunks = await this.readRemoteReconstructionChunks({
3253
+ documentId: params.documentId,
3254
+ revisionKey: params.revisionKey,
3255
+ totalPages,
3256
+ parsedStorageConfig: params.parsedStorageConfig
3257
+ });
3258
+ if (!chunks) {
3259
+ return {
3260
+ documentId: params.documentId,
3261
+ revisionKey: params.revisionKey,
3262
+ completed: false
3263
+ };
3264
+ }
3265
+ const synced = await syncParseResultToParsedDocumentStorage({
3266
+ result: createMinimalParseResultFromRemoteChunks({
3267
+ documentId: params.documentId,
3268
+ revisionKey: params.revisionKey,
3269
+ response,
3270
+ chunks
3271
+ }),
3272
+ storage: params.parsedStorageConfig.storage,
3273
+ documentId: params.documentId,
3274
+ revisionKey: params.revisionKey,
3275
+ source: "remoteReconstruction"
3276
+ });
3277
+ await params.parsedStorageConfig.storage.writeSyncProgress({
3278
+ documentId: params.documentId,
3279
+ revisionKey: params.revisionKey,
3280
+ nextChunkPage: totalPages + 1,
3281
+ status: "completed",
3282
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3283
+ });
3284
+ return {
3285
+ documentId: params.documentId,
3286
+ revisionKey: params.revisionKey,
3287
+ completed: true,
3288
+ commit: synced.commit
3289
+ };
3290
+ }
3291
+ async readRemoteReconstructionChunks(params) {
3292
+ const chunks = [];
3293
+ for (let page = 1; page <= params.totalPages; page += 1) {
3294
+ const object = await params.parsedStorageConfig.storage.readObject({
3295
+ documentId: params.documentId,
3296
+ revisionKey: params.revisionKey,
3297
+ path: getRemoteReconstructionPagePath(page)
3298
+ });
3299
+ if (!object) {
3300
+ return null;
3301
+ }
3302
+ const payload = readJsonObject(object);
3303
+ if (!isDocumentChunkArray(payload.chunks)) {
3304
+ return null;
3305
+ }
3306
+ chunks.push(...payload.chunks);
3307
+ }
3308
+ return chunks;
3309
+ }
3310
+ requireParsedStorageConfig() {
3311
+ if (!this.parsedStorageConfig) {
3312
+ throw new ValidationError("parsed document storage is not configured");
3313
+ }
3314
+ return this.parsedStorageConfig;
3315
+ }
3316
+ async findLocalDocumentByRemoteId(documentId) {
3317
+ const documents = await this.store.listDocuments();
3318
+ return documents.find((document) => document.documentId === documentId);
3319
+ }
3320
+ async getPublishedDocumentJobId(documentId) {
3321
+ const response = await this.client.documents.listChunks(documentId, {
3322
+ page: 1,
3323
+ pageSize: 1,
3324
+ includeAssetUrls: true
3325
+ });
3326
+ if (!response.jobId) {
3327
+ throw new Error(
3328
+ `Cannot sync server document ${documentId}: current published job id was not returned.`
3329
+ );
3330
+ }
3331
+ return response.jobId;
2069
3332
  }
2070
3333
  };
2071
3334
  function normalizeDocumentReference(reference) {
@@ -2084,6 +3347,496 @@ function createLocalDocumentIdForRemote(documentId) {
2084
3347
  const hash = (0, import_crypto2.createHash)("sha256").update(documentId).digest("hex").slice(0, 16);
2085
3348
  return `remote_${hash}`;
2086
3349
  }
3350
+ var defaultParsedStorageScheduler = {
3351
+ schedule(task) {
3352
+ void task();
3353
+ }
3354
+ };
3355
+ function getParsedStorageLimits(limits) {
3356
+ return {
3357
+ remotePageSize: clampLimit(
3358
+ limits?.remotePageSize,
3359
+ DEFAULT_REMOTE_SCAN_PAGE_SIZE,
3360
+ MAX_PAGE_SIZE
3361
+ ),
3362
+ maxPagesPerSync: clampLimit(limits?.maxPagesPerSync, DEFAULT_MAX_SYNC_PAGES, 1e3),
3363
+ syncDeadlineMs: clampLimit(limits?.syncDeadlineMs, DEFAULT_SYNC_DEADLINE_MS, 6e4),
3364
+ grepMaxPages: clampLimit(limits?.grepMaxPages, DEFAULT_GREP_MAX_PAGES, 1e3),
3365
+ grepDeadlineMs: clampLimit(limits?.grepDeadlineMs, DEFAULT_GREP_DEADLINE_MS, 6e4),
3366
+ outlineMaxPages: clampLimit(limits?.outlineMaxPages, DEFAULT_OUTLINE_MAX_PAGES, 1e3),
3367
+ outlineDeadlineMs: clampLimit(limits?.outlineDeadlineMs, DEFAULT_OUTLINE_DEADLINE_MS, 6e4)
3368
+ };
3369
+ }
3370
+ function validateReadParams(params) {
3371
+ const usesPagedDisplay = params.page !== void 0 || params.pageSize !== void 0;
3372
+ if (!usesPagedDisplay) {
3373
+ return;
3374
+ }
3375
+ if (params.sectionPath !== void 0 || params.startChunk !== void 0 || params.endChunk !== void 0 || params.chunkId !== void 0) {
3376
+ throw new ValidationError(
3377
+ "page and pageSize cannot be combined with sectionPath, startChunk, endChunk, or chunkId"
3378
+ );
3379
+ }
3380
+ }
3381
+ function hasScanReadFilters(params) {
3382
+ return params.sectionPath !== void 0 || params.startChunk !== void 0 || params.endChunk !== void 0 || params.chunkId !== void 0;
3383
+ }
3384
+ function getReadPageParams(params) {
3385
+ return {
3386
+ page: clampLimit(params.page, 1, Number.MAX_SAFE_INTEGER),
3387
+ pageSize: clampLimit(params.pageSize ?? params.limit, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE)
3388
+ };
3389
+ }
3390
+ function getRemoteRevisionKey(response) {
3391
+ const revisionKey = response.jobResultId ?? response.jobId;
3392
+ if (!revisionKey) {
3393
+ throw new Error(
3394
+ `Cannot read server document ${response.documentId}: current published revision was not returned.`
3395
+ );
3396
+ }
3397
+ return revisionKey;
3398
+ }
3399
+ function getRemoteReconstructionPagePath(page) {
3400
+ return `${remoteReconstructionPageDirectory}/page-${page}.json`;
3401
+ }
3402
+ function createRemoteKnowledgeDocument(params) {
3403
+ return {
3404
+ localDocumentId: params.documentId,
3405
+ jobId: params.response.jobId ?? params.revisionKey,
3406
+ documentId: params.documentId,
3407
+ namespace: params.response.namespace,
3408
+ sourceFileName: params.documentId,
3409
+ chunkCount: params.response.pagination.total,
3410
+ typeCounts: params.typeCounts ?? countRemoteChunkTypes(params.response.chunks),
3411
+ resultDirectoryPath: `remote:${params.documentId}`,
3412
+ createdAt: /* @__PURE__ */ new Date(0),
3413
+ updatedAt: /* @__PURE__ */ new Date(0)
3414
+ };
3415
+ }
3416
+ function createMinimalParseResultFromRemoteChunks(params) {
3417
+ const chunks = params.chunks.map(toMinimalChunkFromRemoteChunk);
3418
+ const typeCounts = countRemoteChunkTypes(params.chunks);
3419
+ return {
3420
+ manifest: {
3421
+ version: "2.0",
3422
+ jobId: params.response.jobId ?? params.revisionKey,
3423
+ sourceFileName: params.documentId,
3424
+ statistics: {
3425
+ totalChunks: chunks.length,
3426
+ textChunks: typeCounts.text,
3427
+ imageChunks: typeCounts.image,
3428
+ tableChunks: typeCounts.table,
3429
+ pageChunks: typeCounts.page
3430
+ }
3431
+ },
3432
+ chunks,
3433
+ rawZip: Buffer.from(""),
3434
+ namespace: params.response.namespace,
3435
+ documentId: params.documentId,
3436
+ textChunks: chunks.filter(
3437
+ (chunk) => chunk.type === "text"
3438
+ ),
3439
+ imageChunks: chunks.filter(
3440
+ (chunk) => chunk.type === "image"
3441
+ ),
3442
+ tableChunks: chunks.filter(
3443
+ (chunk) => chunk.type === "table"
3444
+ ),
3445
+ pageChunks: chunks.filter(
3446
+ (chunk) => chunk.type === "page"
3447
+ ),
3448
+ jobId: params.response.jobId ?? params.revisionKey,
3449
+ statistics: {
3450
+ totalChunks: chunks.length,
3451
+ textChunks: typeCounts.text,
3452
+ imageChunks: typeCounts.image,
3453
+ tableChunks: typeCounts.table,
3454
+ pageChunks: typeCounts.page
3455
+ },
3456
+ getChunk: (chunkId) => chunks.find((chunk) => chunk.chunkId === chunkId),
3457
+ save: () => Promise.reject(new Error("Minimal remote reconstruction cannot be saved directly."))
3458
+ };
3459
+ }
3460
+ function toMinimalChunkFromRemoteChunk(chunk) {
3461
+ const baseChunk = {
3462
+ chunkId: chunk.chunkId,
3463
+ contentSource: chunk.contentSource ?? void 0,
3464
+ content: chunk.content ?? "",
3465
+ path: chunk.sourceChunkPath ?? chunk.sectionPath ?? "",
3466
+ metadata: cloneMetadata(chunk.metadata)
3467
+ };
3468
+ if (chunk.chunkType === "image") {
3469
+ return {
3470
+ ...baseChunk,
3471
+ type: "image",
3472
+ filePath: chunk.filePath ?? "",
3473
+ assetUrl: chunk.assetUrl ?? void 0,
3474
+ data: Buffer.alloc(0),
3475
+ format: inferContentTypeFromPath2(chunk.filePath ?? "").replace("image/", ""),
3476
+ save: () => Promise.resolve(chunk.filePath ?? "")
3477
+ };
3478
+ }
3479
+ if (chunk.chunkType === "table") {
3480
+ return {
3481
+ ...baseChunk,
3482
+ type: "table",
3483
+ filePath: chunk.filePath ?? "",
3484
+ assetUrl: chunk.assetUrl ?? void 0,
3485
+ html: chunk.content ?? "",
3486
+ save: () => Promise.resolve(chunk.filePath ?? "")
3487
+ };
3488
+ }
3489
+ if (chunk.chunkType === "page") {
3490
+ return {
3491
+ ...baseChunk,
3492
+ type: "page"
3493
+ };
3494
+ }
3495
+ return {
3496
+ ...baseChunk,
3497
+ type: "text"
3498
+ };
3499
+ }
3500
+ function createStoredKnowledgeDocument(params) {
3501
+ return {
3502
+ localDocumentId: params.documentId,
3503
+ jobId: params.storedResult.manifest.jobId || params.revisionKey,
3504
+ documentId: params.documentId,
3505
+ sourceFileName: params.storedResult.manifest.sourceFileName,
3506
+ chunkCount: params.storedResult.chunks.length,
3507
+ typeCounts: params.storedResult.typeCounts,
3508
+ resultDirectoryPath: `parsed-storage:${params.documentId}`,
3509
+ createdAt: new Date(params.storedResult.commit.committedAt),
3510
+ updatedAt: new Date(params.storedResult.commit.committedAt)
3511
+ };
3512
+ }
3513
+ function toRemoteReadChunk(chunk) {
3514
+ const metadata = cloneMetadata(chunk.metadata);
3515
+ return {
3516
+ position: toChunkPosition(chunk),
3517
+ chunkId: chunk.chunkId,
3518
+ chunkType: chunk.chunkType,
3519
+ contentSource: chunk.contentSource ?? void 0,
3520
+ content: chunk.content ?? "",
3521
+ readableContent: getReadableRemoteChunkContent(chunk, metadata),
3522
+ sectionPath: chunk.sectionPath ?? "",
3523
+ sourceChunkPath: chunk.sourceChunkPath ?? chunk.sectionPath ?? "",
3524
+ filePath: chunk.filePath ?? void 0,
3525
+ assetUrl: chunk.assetUrl ?? void 0,
3526
+ pageNumbers: getChunkPageNumbers(metadata),
3527
+ metadata
3528
+ };
3529
+ }
3530
+ function getReadableRemoteChunkContent(chunk, metadata) {
3531
+ if (chunk.chunkType !== "page") {
3532
+ return chunk.content ?? "";
3533
+ }
3534
+ const summary = metadata.summary;
3535
+ return typeof summary === "string" && summary.trim().length > 0 ? summary : chunk.content ?? "";
3536
+ }
3537
+ function toIndexedRemoteChunk(chunk) {
3538
+ const readChunk = toRemoteReadChunk(chunk);
3539
+ return toIndexedReadChunk(readChunk);
3540
+ }
3541
+ function parseStoredResultChunks(params) {
3542
+ const rawChunks = extractStoredChunkRecords(params.payload);
3543
+ return rawChunks.map((record, index) => {
3544
+ const metadata = readRecordMetadata(record);
3545
+ const chunkType = readRecordString(record, "type", "chunkType", "chunk_type");
3546
+ const content = readRecordString(record, "content") ?? "";
3547
+ const sourceChunkPath = readRecordString(record, "path", "sourceChunkPath", "source_chunk_path") ?? "";
3548
+ const chunk = {
3549
+ position: index + 1,
3550
+ chunkId: readRecordString(record, "chunkId", "chunk_id") ?? "",
3551
+ chunkType: isKnowledgeChunkType(chunkType) ? chunkType : "text",
3552
+ contentSource: readRecordString(record, "contentSource", "content_source"),
3553
+ content,
3554
+ readableContent: chunkType === "page" && typeof metadata.summary === "string" && metadata.summary.trim().length > 0 ? metadata.summary : content,
3555
+ sectionPath: normalizeSectionPath(sourceChunkPath, params.sourceFileName),
3556
+ sourceChunkPath,
3557
+ filePath: readRecordString(record, "filePath", "file_path") ?? (typeof metadata.filePath === "string" ? metadata.filePath : void 0),
3558
+ assetUrl: readRecordString(record, "assetUrl", "asset_url"),
3559
+ pageNumbers: getChunkPageNumbers(metadata),
3560
+ metadata
3561
+ };
3562
+ return toIndexedReadChunk(chunk);
3563
+ });
3564
+ }
3565
+ function extractStoredChunkRecords(payload) {
3566
+ if (Array.isArray(payload)) {
3567
+ return payload.filter(isRecord2);
3568
+ }
3569
+ if (isRecord2(payload) && Array.isArray(payload.chunks)) {
3570
+ return payload.chunks.filter(isRecord2);
3571
+ }
3572
+ return [];
3573
+ }
3574
+ function readRecordString(record, ...keys) {
3575
+ for (const key of keys) {
3576
+ const value = record[key];
3577
+ if (typeof value === "string") return value;
3578
+ }
3579
+ return void 0;
3580
+ }
3581
+ function readRecordNumber(record, ...keys) {
3582
+ for (const key of keys) {
3583
+ const value = record[key];
3584
+ if (typeof value === "number") return value;
3585
+ }
3586
+ return void 0;
3587
+ }
3588
+ function readRecordMetadata(record) {
3589
+ const metadata = record.metadata;
3590
+ return isRecord2(metadata) ? keysToCamel(metadata) : {};
3591
+ }
3592
+ function isKnowledgeChunkType(value) {
3593
+ return value === "text" || value === "image" || value === "table" || value === "page";
3594
+ }
3595
+ function toIndexedReadChunk(chunk) {
3596
+ return {
3597
+ source: createSyntheticChunk(chunk),
3598
+ position: chunk.position,
3599
+ chunkId: chunk.chunkId,
3600
+ chunkType: chunk.chunkType,
3601
+ contentSource: chunk.contentSource,
3602
+ content: chunk.content,
3603
+ readableContent: chunk.readableContent,
3604
+ sectionPath: chunk.sectionPath,
3605
+ sourceChunkPath: chunk.sourceChunkPath,
3606
+ filePath: chunk.filePath,
3607
+ assetUrl: chunk.assetUrl,
3608
+ pageNumbers: chunk.pageNumbers,
3609
+ metadata: chunk.metadata
3610
+ };
3611
+ }
3612
+ function createSyntheticChunk(chunk) {
3613
+ return {
3614
+ chunkId: chunk.chunkId,
3615
+ type: chunk.chunkType,
3616
+ contentSource: chunk.contentSource,
3617
+ content: chunk.content,
3618
+ path: chunk.sourceChunkPath,
3619
+ metadata: chunk.metadata
3620
+ };
3621
+ }
3622
+ function toChunkPosition(chunk) {
3623
+ return chunk ? chunk.sortOrder + 1 : 1;
3624
+ }
3625
+ function buildFlatSectionsFromIndexedChunks(chunks) {
3626
+ const byPath = /* @__PURE__ */ new Map();
3627
+ for (const chunk of chunks) {
3628
+ const path4 = chunk.sectionPath || chunk.sourceChunkPath;
3629
+ const existing = byPath.get(path4);
3630
+ if (existing) {
3631
+ addChunkToSection(existing, chunk);
3632
+ } else {
3633
+ byPath.set(path4, createSectionFromChunk(path4, chunk));
3634
+ }
3635
+ }
3636
+ return [...byPath.values()].sort(compareSections);
3637
+ }
3638
+ function matchesRemoteReadScope(chunk, params) {
3639
+ return matchesReadScope(toIndexedRemoteChunk(chunk), params);
3640
+ }
3641
+ function getNextChunkPosition(chunks, selected) {
3642
+ const lastSelected = selected[selected.length - 1];
3643
+ if (!lastSelected || selected.length >= chunks.length) {
3644
+ return void 0;
3645
+ }
3646
+ return chunks[chunks.indexOf(lastSelected) + 1]?.position;
3647
+ }
3648
+ async function applyStoredAssetUrlsToReadChunks(params) {
3649
+ return Promise.all(
3650
+ params.chunks.map(
3651
+ (chunk) => applyStoredAssetUrlsToReadChunk({
3652
+ ...params,
3653
+ chunk
3654
+ })
3655
+ )
3656
+ );
3657
+ }
3658
+ async function applyStoredAssetUrlsToReadChunk(params) {
3659
+ const metadata = cloneMetadata(params.chunk.metadata);
3660
+ const storedAssetUrl = await getStoredObjectUrl({
3661
+ storageConfig: params.storageConfig,
3662
+ documentId: params.documentId,
3663
+ revisionKey: params.revisionKey,
3664
+ path: params.chunk.filePath
3665
+ });
3666
+ const pageAssets = await applyStoredPageAssetUrls({
3667
+ storageConfig: params.storageConfig,
3668
+ documentId: params.documentId,
3669
+ revisionKey: params.revisionKey,
3670
+ metadata
3671
+ });
3672
+ if (pageAssets) {
3673
+ metadata.pageAssets = pageAssets;
3674
+ delete metadata.page_assets;
3675
+ }
3676
+ return {
3677
+ ...params.chunk,
3678
+ assetUrl: storedAssetUrl ?? params.chunk.assetUrl,
3679
+ metadata
3680
+ };
3681
+ }
3682
+ async function applyStoredPageAssetUrls(params) {
3683
+ const pageAssets = getStoredPageAssets(params.metadata);
3684
+ if (!pageAssets) {
3685
+ return void 0;
3686
+ }
3687
+ return Promise.all(
3688
+ pageAssets.map(async (pageAsset) => {
3689
+ if (!isRecord2(pageAsset)) {
3690
+ return pageAsset;
3691
+ }
3692
+ const artifactRef = readRecordString(pageAsset, "artifactRef", "artifact_ref");
3693
+ if (typeof artifactRef !== "string") {
3694
+ return pageAsset;
3695
+ }
3696
+ const storedAssetUrl = await getStoredObjectUrl({
3697
+ storageConfig: params.storageConfig,
3698
+ documentId: params.documentId,
3699
+ revisionKey: params.revisionKey,
3700
+ path: artifactRef
3701
+ });
3702
+ if (storedAssetUrl) {
3703
+ return {
3704
+ ...pageAsset,
3705
+ artifactRef,
3706
+ assetUrl: storedAssetUrl
3707
+ };
3708
+ }
3709
+ return pageAsset;
3710
+ })
3711
+ );
3712
+ }
3713
+ function getStoredPageAssets(metadata) {
3714
+ const pageAssets = toUnknownArray(metadata.pageAssets) ?? toUnknownArray(metadata.page_assets) ?? void 0;
3715
+ return pageAssets?.map(normalizeStoredPageAsset);
3716
+ }
3717
+ function normalizeStoredPageAsset(pageAsset) {
3718
+ if (!isRecord2(pageAsset)) {
3719
+ return pageAsset;
3720
+ }
3721
+ const normalizedPageAsset = { ...pageAsset };
3722
+ delete normalizedPageAsset.page_num;
3723
+ delete normalizedPageAsset.artifact_ref;
3724
+ delete normalizedPageAsset.asset_url;
3725
+ delete normalizedPageAsset.content_type;
3726
+ const pageNum = readRecordNumber(pageAsset, "pageNum", "page_num");
3727
+ const artifactRef = readRecordString(pageAsset, "artifactRef", "artifact_ref");
3728
+ const assetUrl = readRecordString(pageAsset, "assetUrl", "asset_url");
3729
+ const contentType = readRecordString(pageAsset, "contentType", "content_type");
3730
+ return {
3731
+ ...normalizedPageAsset,
3732
+ ...pageNum === void 0 ? {} : { pageNum },
3733
+ ...artifactRef === void 0 ? {} : { artifactRef },
3734
+ ...assetUrl === void 0 ? {} : { assetUrl },
3735
+ ...contentType === void 0 ? {} : { contentType }
3736
+ };
3737
+ }
3738
+ async function getStoredObjectUrl(params) {
3739
+ if (!params.path) {
3740
+ return void 0;
3741
+ }
3742
+ return await params.storageConfig.storage.getObjectUrl?.({
3743
+ documentId: params.documentId,
3744
+ revisionKey: params.revisionKey,
3745
+ path: params.path
3746
+ }) ?? void 0;
3747
+ }
3748
+ function countRemoteChunkTypes(chunks) {
3749
+ return chunks.reduce(
3750
+ (counts, chunk) => {
3751
+ counts[chunk.chunkType] += 1;
3752
+ return counts;
3753
+ },
3754
+ { text: 0, image: 0, table: 0, page: 0 }
3755
+ );
3756
+ }
3757
+ function createContinuationCursor(cursor) {
3758
+ return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
3759
+ }
3760
+ function createGrepContinuationCursor(params) {
3761
+ if (params.matchIndex + 1 < params.chunkMatchCount) {
3762
+ return createContinuationCursor({
3763
+ documentId: params.documentId,
3764
+ revisionKey: params.revisionKey,
3765
+ nextPage: params.response.pagination.page,
3766
+ nextChunkIndex: params.chunkIndex,
3767
+ nextMatchIndex: params.matchIndex + 1
3768
+ });
3769
+ }
3770
+ if (params.chunkIndex + 1 < params.response.chunks.length) {
3771
+ return createContinuationCursor({
3772
+ documentId: params.documentId,
3773
+ revisionKey: params.revisionKey,
3774
+ nextPage: params.response.pagination.page,
3775
+ nextChunkIndex: params.chunkIndex + 1,
3776
+ nextMatchIndex: 0
3777
+ });
3778
+ }
3779
+ if (params.response.pagination.page < params.response.pagination.totalPages) {
3780
+ return createContinuationCursor({
3781
+ documentId: params.documentId,
3782
+ revisionKey: params.revisionKey,
3783
+ nextPage: params.response.pagination.page + 1,
3784
+ nextChunkIndex: 0,
3785
+ nextMatchIndex: 0
3786
+ });
3787
+ }
3788
+ return void 0;
3789
+ }
3790
+ function parseContinuationCursor(value) {
3791
+ if (!value) {
3792
+ return null;
3793
+ }
3794
+ try {
3795
+ const parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
3796
+ if (!isRecord2(parsed)) {
3797
+ return null;
3798
+ }
3799
+ const documentId = parsed.documentId;
3800
+ const revisionKey = parsed.revisionKey;
3801
+ const nextPage = parsed.nextPage;
3802
+ const nextChunkIndex = parsed.nextChunkIndex;
3803
+ const nextMatchIndex = parsed.nextMatchIndex;
3804
+ if (typeof documentId !== "string" || typeof revisionKey !== "string" || typeof nextPage !== "number" || !Number.isInteger(nextPage) || nextPage < 1 || !isOptionalNonNegativeInteger(nextChunkIndex) || !isOptionalNonNegativeInteger(nextMatchIndex)) {
3805
+ return null;
3806
+ }
3807
+ return {
3808
+ documentId,
3809
+ revisionKey,
3810
+ nextPage,
3811
+ nextChunkIndex,
3812
+ nextMatchIndex
3813
+ };
3814
+ } catch {
3815
+ return null;
3816
+ }
3817
+ }
3818
+ function isOptionalNonNegativeInteger(value) {
3819
+ return value === void 0 || typeof value === "number" && Number.isInteger(value) && value >= 0;
3820
+ }
3821
+ function cloneMetadata(metadata) {
3822
+ return { ...metadata };
3823
+ }
3824
+ function toUnknownArray(value) {
3825
+ return Array.isArray(value) ? value : null;
3826
+ }
3827
+ function inferContentTypeFromPath2(filePath) {
3828
+ const extension = filePath.split(".").at(-1)?.toLowerCase();
3829
+ if (extension === "png") return "image/png";
3830
+ if (extension === "jpg" || extension === "jpeg") return "image/jpeg";
3831
+ if (extension === "html" || extension === "htm") return "text/html; charset=utf-8";
3832
+ return "application/octet-stream";
3833
+ }
3834
+ function isRecord2(value) {
3835
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3836
+ }
3837
+ function isDocumentChunkArray(value) {
3838
+ return Array.isArray(value) && value.every(isRecord2);
3839
+ }
2087
3840
  function indexChunks(result) {
2088
3841
  return result.chunks.map((chunk, index) => {
2089
3842
  const filePath = getChunkFilePath2(chunk);
@@ -2099,6 +3852,7 @@ function indexChunks(result) {
2099
3852
  sectionPath: normalizeSectionPath(chunk.path, result.manifest.sourceFileName),
2100
3853
  sourceChunkPath: chunk.path,
2101
3854
  filePath,
3855
+ assetUrl: getChunkAssetUrl(chunk),
2102
3856
  pageNumbers: getChunkPageNumbers(chunk.metadata),
2103
3857
  metadata: chunk.metadata
2104
3858
  };
@@ -2112,12 +3866,11 @@ function getReadableChunkContent(chunk) {
2112
3866
  return typeof summary === "string" && summary.trim().length > 0 ? summary : chunk.content;
2113
3867
  }
2114
3868
  function getChunkPageNumbers(metadata) {
2115
- const value = metadata.pageNums;
2116
- if (!Array.isArray(value)) {
2117
- return void 0;
2118
- }
2119
- const pageNumbers = value.filter(
2120
- (pageNumber) => Number.isInteger(pageNumber) && pageNumber > 0
3869
+ const values = [metadata.pageNums, metadata.page_nums].filter(Array.isArray);
3870
+ const pageNumbers = values.flatMap(
3871
+ (value) => value.filter(
3872
+ (pageNumber) => Number.isInteger(pageNumber) && pageNumber > 0
3873
+ )
2121
3874
  );
2122
3875
  return pageNumbers.length > 0 ? [...new Set(pageNumbers)].sort((left, right) => left - right) : void 0;
2123
3876
  }
@@ -2128,14 +3881,17 @@ function getChunkFilePath2(chunk) {
2128
3881
  const filePath = chunk.metadata.filePath;
2129
3882
  return typeof filePath === "string" ? filePath : void 0;
2130
3883
  }
2131
- function normalizeSectionPath(path3, sourceFileName) {
2132
- if (!path3) {
3884
+ function getChunkAssetUrl(chunk) {
3885
+ return chunk.type === "image" || chunk.type === "table" ? chunk.assetUrl : void 0;
3886
+ }
3887
+ function normalizeSectionPath(path4, sourceFileName) {
3888
+ if (!path4) {
2133
3889
  return "";
2134
3890
  }
2135
- if (path3.startsWith("images/") || path3.startsWith("tables/")) {
2136
- return path3;
3891
+ if (path4.startsWith("images/") || path4.startsWith("tables/")) {
3892
+ return path4;
2137
3893
  }
2138
- const parts = path3.split("/").filter(Boolean);
3894
+ const parts = path4.split("/").filter(Boolean);
2139
3895
  if (sourceFileName) {
2140
3896
  const fileNameIndex = parts.indexOf(sourceFileName);
2141
3897
  if (fileNameIndex >= 0) {
@@ -2157,12 +3913,12 @@ function buildFlatSections(result, chunks) {
2157
3913
  }
2158
3914
  const byPath = /* @__PURE__ */ new Map();
2159
3915
  for (const chunk of chunks) {
2160
- const path3 = chunk.sectionPath || chunk.sourceChunkPath;
2161
- const existing = byPath.get(path3);
3916
+ const path4 = chunk.sectionPath || chunk.sourceChunkPath;
3917
+ const existing = byPath.get(path4);
2162
3918
  if (existing) {
2163
3919
  addChunkToSection(existing, chunk);
2164
3920
  } else {
2165
- byPath.set(path3, createSectionFromChunk(path3, chunk));
3921
+ byPath.set(path4, createSectionFromChunk(path4, chunk));
2166
3922
  }
2167
3923
  }
2168
3924
  return [...byPath.values()].sort(compareSections);
@@ -2296,6 +4052,7 @@ function toReadChunk(chunk) {
2296
4052
  sectionPath: chunk.sectionPath,
2297
4053
  sourceChunkPath: chunk.sourceChunkPath,
2298
4054
  filePath: chunk.filePath,
4055
+ assetUrl: chunk.assetUrl,
2299
4056
  pageNumbers: chunk.pageNumbers,
2300
4057
  metadata: chunk.metadata
2301
4058
  };
@@ -2472,7 +4229,12 @@ var Knowhere = class {
2472
4229
  const result = await this.jobs.load(jobResult, {
2473
4230
  verifyChecksum: params.verifyChecksum
2474
4231
  });
2475
- return enrichParseResult(result, jobResult);
4232
+ const enrichedResult = enrichParseResult(result, {
4233
+ namespace: jobResult.namespace,
4234
+ documentId: jobResult.documentId ?? params.documentId
4235
+ });
4236
+ const storedResult = await storeParseResultAssets(enrichedResult, params.storageAdapter);
4237
+ return storedResult.result;
2476
4238
  }
2477
4239
  /**
2478
4240
  * Start a parse job and return immediately after the URL job is created or
@@ -2515,6 +4277,160 @@ var Knowhere = class {
2515
4277
  return job;
2516
4278
  }
2517
4279
  };
4280
+
4281
+ // src/storage/disk-parsed-document-storage.ts
4282
+ var import_crypto3 = require("crypto");
4283
+ var import_fs4 = require("fs");
4284
+ var import_path4 = __toESM(require("path"));
4285
+ var syncProgressPath = ".knowhere-sdk/sync-progress.json";
4286
+ var DiskParsedDocumentStorage = class {
4287
+ rootDirectory;
4288
+ constructor(rootDirectory) {
4289
+ this.rootDirectory = rootDirectory;
4290
+ }
4291
+ async readObject(params) {
4292
+ const objectPath = this.getObjectPath(params);
4293
+ try {
4294
+ const body = await import_fs4.promises.readFile(objectPath);
4295
+ const metadata = await this.readObjectMetadata(objectPath);
4296
+ return {
4297
+ ...params,
4298
+ body,
4299
+ contentType: metadata?.contentType,
4300
+ contentLength: body.byteLength,
4301
+ metadata: metadata?.metadata,
4302
+ url: toFileUrl(objectPath)
4303
+ };
4304
+ } catch (error) {
4305
+ if (isMissingFileError3(error)) {
4306
+ return null;
4307
+ }
4308
+ throw error;
4309
+ }
4310
+ }
4311
+ async writeObject(params) {
4312
+ const objectPath = this.getObjectPath(params);
4313
+ await import_fs4.promises.mkdir(import_path4.default.dirname(objectPath), { recursive: true });
4314
+ await import_fs4.promises.writeFile(objectPath, params.body);
4315
+ await this.writeObjectMetadata(objectPath, {
4316
+ contentType: params.contentType,
4317
+ metadata: params.metadata
4318
+ });
4319
+ return {
4320
+ documentId: params.documentId,
4321
+ revisionKey: params.revisionKey,
4322
+ path: params.path,
4323
+ url: toFileUrl(objectPath)
4324
+ };
4325
+ }
4326
+ async headObject(params) {
4327
+ const objectPath = this.getObjectPath(params);
4328
+ try {
4329
+ const stat = await import_fs4.promises.stat(objectPath);
4330
+ const metadata = await this.readObjectMetadata(objectPath);
4331
+ return {
4332
+ ...params,
4333
+ contentType: metadata?.contentType,
4334
+ contentLength: stat.size,
4335
+ metadata: metadata?.metadata,
4336
+ url: toFileUrl(objectPath)
4337
+ };
4338
+ } catch (error) {
4339
+ if (isMissingFileError3(error)) {
4340
+ return null;
4341
+ }
4342
+ throw error;
4343
+ }
4344
+ }
4345
+ async getObjectUrl(params) {
4346
+ const head = await this.headObject(params);
4347
+ return head?.url ?? null;
4348
+ }
4349
+ async deletePrefix(params) {
4350
+ await import_fs4.promises.rm(this.getRevisionDirectory(params.documentId, params.revisionKey), {
4351
+ recursive: true,
4352
+ force: true
4353
+ });
4354
+ }
4355
+ async readSyncProgress(params) {
4356
+ return this.readOptionalJson({
4357
+ ...params,
4358
+ path: syncProgressPath
4359
+ });
4360
+ }
4361
+ async writeSyncProgress(params) {
4362
+ await this.writeObject({
4363
+ documentId: params.documentId,
4364
+ revisionKey: params.revisionKey,
4365
+ path: syncProgressPath,
4366
+ body: Buffer.from(JSON.stringify(params, null, 2), "utf8"),
4367
+ contentType: "application/json; charset=utf-8"
4368
+ });
4369
+ }
4370
+ getObjectPath(params) {
4371
+ const revisionDirectory = this.getRevisionDirectory(params.documentId, params.revisionKey);
4372
+ const objectPath = import_path4.default.resolve(revisionDirectory, normalizeRelativeStoragePath(params.path));
4373
+ if (!isPathInsideDirectory2(objectPath, revisionDirectory)) {
4374
+ throw new Error(`Parsed document object path resolves outside storage: ${params.path}`);
4375
+ }
4376
+ return objectPath;
4377
+ }
4378
+ getRevisionDirectory(documentId, revisionKey) {
4379
+ return import_path4.default.join(this.rootDirectory, hashPathPart(documentId), hashPathPart(revisionKey));
4380
+ }
4381
+ async readOptionalJson(params) {
4382
+ const object = await this.readObject(params);
4383
+ if (!object) {
4384
+ return null;
4385
+ }
4386
+ return JSON.parse(Buffer.from(object.body).toString("utf8"));
4387
+ }
4388
+ async readObjectMetadata(filePath) {
4389
+ try {
4390
+ return JSON.parse(
4391
+ await import_fs4.promises.readFile(getMetadataPath(filePath), "utf8")
4392
+ );
4393
+ } catch (error) {
4394
+ if (isMissingFileError3(error)) {
4395
+ return null;
4396
+ }
4397
+ throw error;
4398
+ }
4399
+ }
4400
+ async writeObjectMetadata(filePath, metadata) {
4401
+ await import_fs4.promises.writeFile(getMetadataPath(filePath), JSON.stringify(metadata, null, 2), {
4402
+ encoding: "utf8",
4403
+ flag: "w"
4404
+ });
4405
+ }
4406
+ };
4407
+ function getMetadataPath(filePath) {
4408
+ return `${filePath}.metadata.json`;
4409
+ }
4410
+ function hashPathPart(value) {
4411
+ const normalized = value.trim();
4412
+ if (normalized.length === 0) {
4413
+ throw new Error("Parsed document storage path part must be a non-empty value.");
4414
+ }
4415
+ return (0, import_crypto3.createHash)("sha256").update(normalized).digest("hex").slice(0, 24);
4416
+ }
4417
+ function normalizeRelativeStoragePath(value) {
4418
+ const normalized = value.replaceAll("\\", "/").replace(/^\.\/+/, "");
4419
+ if (normalized.length === 0 || normalized.includes("\0") || normalized.startsWith("/") || normalized.split("/").some((part) => part.length === 0 || part === "." || part === "..")) {
4420
+ throw new Error(`Invalid parsed storage path: ${value}`);
4421
+ }
4422
+ return normalized;
4423
+ }
4424
+ function isPathInsideDirectory2(filePath, directory) {
4425
+ const relative = import_path4.default.relative(directory, filePath);
4426
+ return relative === "" || !relative.startsWith("..") && !import_path4.default.isAbsolute(relative);
4427
+ }
4428
+ function toFileUrl(filePath) {
4429
+ return `file://${filePath}`;
4430
+ }
4431
+ function isMissingFileError3(error) {
4432
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
4433
+ }
2518
4434
  // Annotate the CommonJS export names for ESM import in node:
2519
4435
  0 && (module.exports = {
2520
4436
  APIError,
@@ -2522,6 +4438,7 @@ var Knowhere = class {
2522
4438
  BadRequestError,
2523
4439
  ChecksumError,
2524
4440
  ConflictError,
4441
+ DiskParsedDocumentStorage,
2525
4442
  Documents,
2526
4443
  GatewayTimeoutError,
2527
4444
  InternalServerError,
@@ -2542,5 +4459,7 @@ var Knowhere = class {
2542
4459
  ServiceUnavailableError,
2543
4460
  TimeoutError,
2544
4461
  VERSION,
2545
- ValidationError
4462
+ ValidationError,
4463
+ storeParseResultAssets,
4464
+ syncParseResultToParsedDocumentStorage
2546
4465
  });