@ontos-ai/knowhere-sdk 0.10.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.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,6 +759,9 @@ var BaseResource = class {
756
759
  constructor(httpClient) {
757
760
  this.httpClient = httpClient;
758
761
  }
762
+ endpoint(path4) {
763
+ return `/v2${path4.startsWith("/") ? path4 : `/${path4}`}`;
764
+ }
759
765
  };
760
766
 
761
767
  // src/lib/upload.ts
@@ -806,7 +812,7 @@ async function pollJobStatus(httpClient, jobId, options) {
806
812
  if (signal?.aborted) {
807
813
  throw new Error("Polling aborted");
808
814
  }
809
- const jobResult = await httpClient.get(`/v1/jobs/${jobId}`);
815
+ const jobResult = await httpClient.get(`/v2/jobs/${jobId}`);
810
816
  enrichJobResult(jobResult);
811
817
  const elapsed = Date.now() - startTime;
812
818
  const elapsedSeconds = Math.floor(elapsed / 1e3);
@@ -970,17 +976,28 @@ async function saveExpandedParseResult(result, directory) {
970
976
  if (result.rawZip.length > 0) {
971
977
  const didExtractZip = await tryExtractRawZip(result.rawZip, directory);
972
978
  if (didExtractZip) {
979
+ await writeExpandedParseResultMetadata(result, directory);
973
980
  return directory;
974
981
  }
975
982
  }
976
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) {
977
994
  await import_fs2.promises.writeFile((0, import_path.join)(directory, "manifest.json"), JSON.stringify(result.manifest, null, 2));
978
995
  if (result.docNav) {
979
996
  await import_fs2.promises.writeFile((0, import_path.join)(directory, "doc_nav.json"), JSON.stringify(result.docNav, null, 2));
980
997
  }
981
998
  await import_fs2.promises.writeFile(
982
999
  (0, import_path.join)(directory, "chunks.json"),
983
- JSON.stringify(serializeChunks(result.chunks), null, 2)
1000
+ JSON.stringify(keysToSnake(serializeChunks(result.chunks)), null, 2)
984
1001
  );
985
1002
  if (result.chunksSlim) {
986
1003
  await import_fs2.promises.writeFile(
@@ -1009,13 +1026,6 @@ async function saveExpandedParseResult(result, directory) {
1009
1026
  if (result.hierarchyViewHtml) {
1010
1027
  await import_fs2.promises.writeFile((0, import_path.join)(directory, "hierarchy_view.html"), result.hierarchyViewHtml);
1011
1028
  }
1012
- for (const imageChunk of result.imageChunks) {
1013
- await writeBinaryAsset(directory, imageChunk.filePath, imageChunk.data);
1014
- }
1015
- for (const tableChunk of result.tableChunks) {
1016
- await writeTextAsset(directory, tableChunk.filePath, tableChunk.html);
1017
- }
1018
- return directory;
1019
1029
  }
1020
1030
  async function tryExtractRawZip(zipBuffer, directory) {
1021
1031
  try {
@@ -1067,6 +1077,9 @@ function createParseResult(parts) {
1067
1077
  get tableChunks() {
1068
1078
  return chunks.filter((c) => c.type === "table");
1069
1079
  },
1080
+ get pageChunks() {
1081
+ return chunks.filter((c) => c.type === "page");
1082
+ },
1070
1083
  get jobId() {
1071
1084
  return manifest.jobId;
1072
1085
  },
@@ -1144,18 +1157,32 @@ function buildTextChunk(chunkData) {
1144
1157
  return {
1145
1158
  chunkId: chunkData.chunkId ?? "",
1146
1159
  type: "text",
1160
+ contentSource: chunkData.contentSource ?? "content",
1147
1161
  content: chunkData.content ?? "",
1148
1162
  path: chunkData.path ?? "",
1149
1163
  metadata: chunkData.metadata ?? {}
1150
1164
  };
1151
1165
  }
1166
+ function buildPageChunk(chunkData) {
1167
+ const metadata = chunkData.metadata ?? {};
1168
+ return {
1169
+ chunkId: chunkData.chunkId ?? "",
1170
+ type: "page",
1171
+ contentSource: chunkData.contentSource ?? "summary",
1172
+ content: chunkData.content ?? "",
1173
+ path: chunkData.path ?? "",
1174
+ metadata
1175
+ };
1176
+ }
1152
1177
  function buildImageChunk(chunkData, filePath, imageBuffer) {
1153
1178
  return {
1154
1179
  chunkId: chunkData.chunkId ?? "",
1155
1180
  type: "image",
1181
+ contentSource: chunkData.contentSource,
1156
1182
  content: chunkData.content ?? "",
1157
1183
  path: chunkData.path ?? "",
1158
1184
  filePath,
1185
+ assetUrl: chunkData.assetUrl,
1159
1186
  data: imageBuffer,
1160
1187
  metadata: chunkData.metadata ?? {},
1161
1188
  get format() {
@@ -1170,9 +1197,11 @@ function buildTableChunk(chunkData, filePath, html) {
1170
1197
  return {
1171
1198
  chunkId: chunkData.chunkId ?? "",
1172
1199
  type: "table",
1200
+ contentSource: chunkData.contentSource,
1173
1201
  content: chunkData.content ?? "",
1174
1202
  path: chunkData.path ?? "",
1175
1203
  filePath,
1204
+ assetUrl: chunkData.assetUrl,
1176
1205
  html,
1177
1206
  metadata: chunkData.metadata ?? {},
1178
1207
  async save(directory) {
@@ -1184,6 +1213,9 @@ async function processChunk(zip, chunkData) {
1184
1213
  if (chunkData.type === "text") {
1185
1214
  return buildTextChunk(chunkData);
1186
1215
  }
1216
+ if (chunkData.type === "page") {
1217
+ return buildPageChunk(chunkData);
1218
+ }
1187
1219
  if (chunkData.type === "image") {
1188
1220
  const filePath = getChunkFilePath(chunkData);
1189
1221
  if (!filePath) {
@@ -1216,6 +1248,9 @@ async function processDirectoryChunk(directory, chunkData) {
1216
1248
  if (chunkData.type === "text") {
1217
1249
  return buildTextChunk(chunkData);
1218
1250
  }
1251
+ if (chunkData.type === "page") {
1252
+ return buildPageChunk(chunkData);
1253
+ }
1219
1254
  if (chunkData.type === "image") {
1220
1255
  const filePath = getChunkFilePath(chunkData);
1221
1256
  if (!filePath) {
@@ -1254,12 +1289,14 @@ function serializeChunks(chunks) {
1254
1289
  const rawChunk = {
1255
1290
  chunkId: chunk.chunkId,
1256
1291
  type: chunk.type,
1292
+ contentSource: chunk.contentSource,
1257
1293
  content: chunk.content,
1258
1294
  path: chunk.path,
1259
1295
  metadata: chunk.metadata
1260
1296
  };
1261
1297
  if (chunk.type === "image" || chunk.type === "table") {
1262
1298
  rawChunk.filePath = chunk.filePath;
1299
+ rawChunk.assetUrl = chunk.assetUrl;
1263
1300
  }
1264
1301
  return rawChunk;
1265
1302
  })
@@ -1322,7 +1359,7 @@ var Jobs = class extends BaseResource {
1322
1359
  * Create a new parsing job
1323
1360
  */
1324
1361
  async create(params) {
1325
- const job = await this.httpClient.post("/v1/jobs", params);
1362
+ const job = await this.httpClient.post(this.endpoint("/jobs"), params);
1326
1363
  if (job.uploadUrl) {
1327
1364
  this.pendingUploadJobs.set(job.jobId, job);
1328
1365
  }
@@ -1332,7 +1369,7 @@ var Jobs = class extends BaseResource {
1332
1369
  * Get job status
1333
1370
  */
1334
1371
  async get(jobId) {
1335
- const jobResult = await this.httpClient.get(`/v1/jobs/${jobId}`);
1372
+ const jobResult = await this.httpClient.get(this.endpoint(`/jobs/${jobId}`));
1336
1373
  enrichJobResult(jobResult);
1337
1374
  return jobResult;
1338
1375
  }
@@ -1428,7 +1465,7 @@ var Retrieval = class extends BaseResource {
1428
1465
  * Query published documents.
1429
1466
  */
1430
1467
  async query(params) {
1431
- return this.httpClient.post("/v1/retrieval/query", params);
1468
+ return this.httpClient.post(this.endpoint("/retrieval/query"), params);
1432
1469
  }
1433
1470
  };
1434
1471
 
@@ -1439,7 +1476,7 @@ var Documents = class extends BaseResource {
1439
1476
  */
1440
1477
  async list(params) {
1441
1478
  const response = await this.httpClient.get(
1442
- "/v1/documents",
1479
+ this.endpoint("/documents"),
1443
1480
  this.createDocumentListRequestConfig(params)
1444
1481
  );
1445
1482
  return this.normalizeDocumentListResponse(response);
@@ -1448,14 +1485,14 @@ var Documents = class extends BaseResource {
1448
1485
  * Get one canonical document by ID.
1449
1486
  */
1450
1487
  async get(documentId) {
1451
- return this.httpClient.get(`/v1/documents/${documentId}`);
1488
+ return this.httpClient.get(this.endpoint(`/documents/${documentId}`));
1452
1489
  }
1453
1490
  /**
1454
1491
  * List current-revision chunks for one canonical document.
1455
1492
  */
1456
1493
  async listChunks(documentId, params) {
1457
1494
  return this.httpClient.get(
1458
- `/v1/documents/${documentId}/chunks`,
1495
+ this.endpoint(`/documents/${documentId}/chunks`),
1459
1496
  this.createChunkListRequestConfig(params)
1460
1497
  );
1461
1498
  }
@@ -1464,15 +1501,23 @@ var Documents = class extends BaseResource {
1464
1501
  */
1465
1502
  async getChunk(documentId, documentChunkId, params) {
1466
1503
  return this.httpClient.get(
1467
- `/v1/documents/${documentId}/chunks/${documentChunkId}`,
1504
+ this.endpoint(`/documents/${documentId}/chunks/${documentChunkId}`),
1468
1505
  this.createChunkGetRequestConfig(params)
1469
1506
  );
1470
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
+ }
1471
1516
  /**
1472
1517
  * Archive one canonical document by ID.
1473
1518
  */
1474
1519
  async archive(documentId) {
1475
- return this.httpClient.post(`/v1/documents/${documentId}/archive`);
1520
+ return this.httpClient.post(this.endpoint(`/documents/${documentId}/archive`));
1476
1521
  }
1477
1522
  createDocumentListRequestConfig(params) {
1478
1523
  if (!params) {
@@ -1540,6 +1585,514 @@ var Documents = class extends BaseResource {
1540
1585
  // src/knowledge/knowledge.ts
1541
1586
  var import_crypto2 = require("crypto");
1542
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
+ var snapshotManifestKey = "manifest/current.json";
1594
+ var snapshotIndexKey = "index.json";
1595
+ var defaultSnapshotChunkPageSize = 200;
1596
+ var snapshotJsonContentType = "application/json; charset=utf-8";
1597
+ async function storeParseResultAssets(result, options) {
1598
+ if (!options) {
1599
+ return {
1600
+ result,
1601
+ assetUrlsByFilePath: {}
1602
+ };
1603
+ }
1604
+ const normalizedKeyPrefix = normalizeStorageKeyPrefix(options.keyPrefix);
1605
+ const assets = dedupeStorageAssets(await collectStorageAssets(result, normalizedKeyPrefix));
1606
+ const assetUrlsByFilePath = {};
1607
+ for (const asset of assets) {
1608
+ const storedUrl = await storeAsset(asset, options.adapter, options.skipExisting ?? true);
1609
+ if (storedUrl) {
1610
+ assetUrlsByFilePath[asset.sourcePath] = storedUrl;
1611
+ }
1612
+ }
1613
+ const normalizedResult = Object.keys(assetUrlsByFilePath).length === 0 ? result : rewriteResultAssetUrls(result, assetUrlsByFilePath);
1614
+ const snapshot = await storeParsedSnapshot({
1615
+ result: normalizedResult,
1616
+ assetUrlsByFilePath,
1617
+ keyPrefix: normalizedKeyPrefix,
1618
+ options
1619
+ });
1620
+ return {
1621
+ result: normalizedResult,
1622
+ assetUrlsByFilePath,
1623
+ snapshot
1624
+ };
1625
+ }
1626
+ async function storeParsedSnapshot(input) {
1627
+ const pageSize = normalizeSnapshotChunkPageSize(input.options.chunkPageSize);
1628
+ const totalChunks = input.result.chunks.length;
1629
+ const totalPages = Math.max(1, Math.ceil(totalChunks / pageSize));
1630
+ const chunkPageReferences = [];
1631
+ const chunkPageUrlsByPage = {};
1632
+ for (let page = 1; page <= totalPages; page += 1) {
1633
+ const pageChunks = input.result.chunks.slice((page - 1) * pageSize, page * pageSize);
1634
+ const pageKey = `${input.keyPrefix}/chunks/page-${page}.json`;
1635
+ const chunkPage = {
1636
+ version: 1,
1637
+ jobId: input.result.jobId,
1638
+ revisionKey: input.options.revisionKey ?? input.result.jobId,
1639
+ documentId: input.result.documentId,
1640
+ namespace: input.result.namespace,
1641
+ sourceFileName: input.result.manifest.sourceFileName,
1642
+ page,
1643
+ pageSize,
1644
+ total: totalChunks,
1645
+ totalPages,
1646
+ chunks: pageChunks.map(
1647
+ (chunk, index) => toSnapshotChunk(chunk, (page - 1) * pageSize + index + 1)
1648
+ )
1649
+ };
1650
+ const pageUrl = await writeJsonObject({
1651
+ key: pageKey,
1652
+ value: chunkPage,
1653
+ adapter: input.options.adapter
1654
+ });
1655
+ if (pageUrl) chunkPageUrlsByPage[page] = pageUrl;
1656
+ chunkPageReferences.push({
1657
+ page,
1658
+ pageSize,
1659
+ chunkCount: pageChunks.length,
1660
+ key: pageKey,
1661
+ url: pageUrl
1662
+ });
1663
+ }
1664
+ const manifest = {
1665
+ version: 1,
1666
+ kind: "knowhere-parsed-result-snapshot",
1667
+ jobId: input.result.jobId,
1668
+ revisionKey: input.options.revisionKey ?? input.result.jobId,
1669
+ documentId: input.result.documentId,
1670
+ namespace: input.result.namespace,
1671
+ sourceFileName: input.result.manifest.sourceFileName,
1672
+ totalChunks,
1673
+ typeCounts: countSnapshotChunkTypes(input.result.chunks),
1674
+ chunkPageSize: pageSize,
1675
+ chunkPages: chunkPageReferences,
1676
+ assetUrlsByFilePath: input.assetUrlsByFilePath,
1677
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1678
+ };
1679
+ const manifestKey = `${input.keyPrefix}/${snapshotManifestKey}`;
1680
+ const indexKey = `${input.keyPrefix}/${snapshotIndexKey}`;
1681
+ const manifestUrl = await writeJsonObject({
1682
+ key: manifestKey,
1683
+ value: manifest,
1684
+ adapter: input.options.adapter
1685
+ });
1686
+ const indexUrl = await writeJsonObject({
1687
+ key: indexKey,
1688
+ value: manifest,
1689
+ adapter: input.options.adapter
1690
+ });
1691
+ return {
1692
+ manifest,
1693
+ manifestKey,
1694
+ manifestUrl,
1695
+ indexKey,
1696
+ indexUrl,
1697
+ chunkPageUrlsByPage
1698
+ };
1699
+ }
1700
+ function countSnapshotChunkTypes(chunks) {
1701
+ return chunks.reduce(
1702
+ (counts, chunk) => {
1703
+ counts[chunk.type] += 1;
1704
+ return counts;
1705
+ },
1706
+ { text: 0, image: 0, table: 0, page: 0 }
1707
+ );
1708
+ }
1709
+ async function writeJsonObject(input) {
1710
+ const result = await input.adapter.writeObject({
1711
+ key: input.key,
1712
+ body: Buffer.from(JSON.stringify(input.value, null, 2), "utf8"),
1713
+ contentType: snapshotJsonContentType,
1714
+ metadata: {
1715
+ kind: "knowhere-parsed-result-snapshot"
1716
+ }
1717
+ });
1718
+ return result.url ?? await input.adapter.getObjectUrl?.(result.key) ?? void 0;
1719
+ }
1720
+ function normalizeSnapshotChunkPageSize(value) {
1721
+ if (!Number.isInteger(value) || value === void 0) return defaultSnapshotChunkPageSize;
1722
+ return Math.min(Math.max(value, 1), 1e3);
1723
+ }
1724
+ function toSnapshotChunk(chunk, position) {
1725
+ const filePath = getSnapshotChunkFilePath(chunk);
1726
+ return {
1727
+ id: chunk.chunkId,
1728
+ chunkId: chunk.chunkId,
1729
+ chunkType: chunk.type,
1730
+ contentSource: chunk.contentSource,
1731
+ content: chunk.content,
1732
+ sectionPath: normalizeSnapshotSectionPath(chunk.path),
1733
+ sourceChunkPath: chunk.path,
1734
+ filePath,
1735
+ sortOrder: position,
1736
+ metadata: cloneChunkMetadata(chunk.metadata),
1737
+ assetUrl: getSnapshotChunkAssetUrl(chunk)
1738
+ };
1739
+ }
1740
+ function normalizeSnapshotSectionPath(path4) {
1741
+ return path4.length > 0 ? path4 : void 0;
1742
+ }
1743
+ function getSnapshotChunkFilePath(chunk) {
1744
+ if (chunk.type === "image" || chunk.type === "table") {
1745
+ return chunk.filePath;
1746
+ }
1747
+ const filePath = chunk.metadata.filePath;
1748
+ return typeof filePath === "string" ? filePath : void 0;
1749
+ }
1750
+ function getSnapshotChunkAssetUrl(chunk) {
1751
+ return chunk.type === "image" || chunk.type === "table" ? chunk.assetUrl : void 0;
1752
+ }
1753
+ function cloneChunkMetadata(metadata) {
1754
+ return { ...metadata };
1755
+ }
1756
+ async function collectStorageAssets(result, keyPrefix) {
1757
+ const resultZip = await loadResultZipForPageCitationAssets(result);
1758
+ return [
1759
+ ...result.imageChunks.flatMap((chunk) => collectImageStorageAsset(chunk, keyPrefix)),
1760
+ ...result.tableChunks.flatMap((chunk) => collectTableStorageAsset(chunk, keyPrefix)),
1761
+ ...resultZip ? await collectPageCitationStorageAssets(result.pageChunks, keyPrefix, resultZip) : []
1762
+ ];
1763
+ }
1764
+ function collectImageStorageAsset(chunk, keyPrefix) {
1765
+ const sourcePath = normalizeResultAssetPath(chunk.filePath);
1766
+ if (!sourcePath) return [];
1767
+ return [
1768
+ {
1769
+ sourcePath,
1770
+ storageKey: `${keyPrefix}/${sourcePath}`,
1771
+ body: chunk.data,
1772
+ contentType: getImageContentType(chunk.filePath),
1773
+ metadata: {
1774
+ chunkId: chunk.chunkId,
1775
+ chunkType: chunk.type,
1776
+ sourcePath
1777
+ }
1778
+ }
1779
+ ];
1780
+ }
1781
+ function collectTableStorageAsset(chunk, keyPrefix) {
1782
+ const sourcePath = normalizeResultAssetPath(chunk.filePath);
1783
+ if (!sourcePath) return [];
1784
+ return [
1785
+ {
1786
+ sourcePath,
1787
+ storageKey: `${keyPrefix}/${sourcePath}`,
1788
+ body: Buffer.from(chunk.html, "utf8"),
1789
+ contentType: tableContentType,
1790
+ metadata: {
1791
+ chunkId: chunk.chunkId,
1792
+ chunkType: chunk.type,
1793
+ sourcePath
1794
+ }
1795
+ }
1796
+ ];
1797
+ }
1798
+ async function collectPageCitationStorageAssets(chunks, keyPrefix, resultZip) {
1799
+ const assets = [];
1800
+ for (const chunk of chunks) {
1801
+ const pageAssets = parsePageCitationAssets(chunk.metadata[pageCitationAssetsMetadataKey]);
1802
+ for (const pageAsset of pageAssets) {
1803
+ const sourcePath = normalizeResultAssetPath(pageAsset.artifactRef);
1804
+ if (!sourcePath) continue;
1805
+ const zipEntry = resultZip.file(sourcePath);
1806
+ if (!zipEntry) continue;
1807
+ assets.push({
1808
+ sourcePath,
1809
+ storageKey: `${keyPrefix}/${sourcePath}`,
1810
+ body: await zipEntry.async("uint8array"),
1811
+ contentType: pageAsset.contentType,
1812
+ metadata: getPageCitationAssetMetadata(chunk, pageAsset, sourcePath)
1813
+ });
1814
+ }
1815
+ }
1816
+ return assets;
1817
+ }
1818
+ function getPageCitationAssetMetadata(chunk, pageAsset, sourcePath) {
1819
+ return {
1820
+ chunkId: chunk.chunkId,
1821
+ chunkType: chunk.type,
1822
+ pageNum: String(pageAsset.pageNum),
1823
+ sourcePath
1824
+ };
1825
+ }
1826
+ async function loadResultZipForPageCitationAssets(result) {
1827
+ if (!hasPageCitationAssetReferences(result) || result.rawZip.length === 0) {
1828
+ return null;
1829
+ }
1830
+ try {
1831
+ return await import_jszip2.default.loadAsync(result.rawZip);
1832
+ } catch (cause) {
1833
+ throw new Error("Unable to read page citation assets from the Knowhere result ZIP.", {
1834
+ cause
1835
+ });
1836
+ }
1837
+ }
1838
+ function hasPageCitationAssetReferences(result) {
1839
+ return result.pageChunks.some(
1840
+ (chunk) => parsePageCitationAssets(chunk.metadata[pageCitationAssetsMetadataKey]).length > 0
1841
+ );
1842
+ }
1843
+ function dedupeStorageAssets(assets) {
1844
+ const assetsBySourcePath = /* @__PURE__ */ new Map();
1845
+ for (const asset of assets) {
1846
+ if (!assetsBySourcePath.has(asset.sourcePath)) {
1847
+ assetsBySourcePath.set(asset.sourcePath, asset);
1848
+ }
1849
+ }
1850
+ return [...assetsBySourcePath.values()];
1851
+ }
1852
+ async function storeAsset(asset, adapter, skipExisting) {
1853
+ if (skipExisting && adapter.headObject) {
1854
+ const existing = await adapter.headObject(asset.storageKey);
1855
+ if (existing) {
1856
+ const existingUrl = existing.url ?? await adapter.getObjectUrl?.(asset.storageKey);
1857
+ if (existingUrl) return existingUrl;
1858
+ }
1859
+ }
1860
+ const writeResult = await adapter.writeObject({
1861
+ key: asset.storageKey,
1862
+ body: asset.body,
1863
+ contentType: asset.contentType,
1864
+ metadata: asset.metadata
1865
+ });
1866
+ return writeResult.url ?? await adapter.getObjectUrl?.(writeResult.key) ?? null;
1867
+ }
1868
+ function rewriteResultAssetUrls(result, assetUrlsByFilePath) {
1869
+ const chunks = result.chunks.map((chunk) => rewriteChunkAssetUrls(chunk, assetUrlsByFilePath));
1870
+ result.chunks.splice(0, result.chunks.length, ...chunks);
1871
+ refreshStaticChunkCollections(result);
1872
+ return result;
1873
+ }
1874
+ function rewriteChunkAssetUrls(chunk, assetUrlsByPath) {
1875
+ if (chunk.type === "image" || chunk.type === "table") {
1876
+ const sourcePath = normalizeResultAssetPath(chunk.filePath);
1877
+ const assetUrl = sourcePath ? assetUrlsByPath[sourcePath] : void 0;
1878
+ if (!assetUrl) return chunk;
1879
+ return {
1880
+ ...chunk,
1881
+ assetUrl
1882
+ };
1883
+ }
1884
+ if (chunk.type === "page") {
1885
+ const pageAssets = parsePageCitationAssets(chunk.metadata[pageCitationAssetsMetadataKey]);
1886
+ if (pageAssets.length === 0) return chunk;
1887
+ const rewrittenPageAssets = pageAssets.map((pageAsset) => {
1888
+ const sourcePath = normalizeResultAssetPath(pageAsset.artifactRef);
1889
+ const assetUrl = sourcePath ? assetUrlsByPath[sourcePath] : void 0;
1890
+ return assetUrl ? { ...pageAsset, assetUrl } : pageAsset;
1891
+ });
1892
+ return {
1893
+ ...chunk,
1894
+ metadata: {
1895
+ ...chunk.metadata,
1896
+ [pageCitationAssetsMetadataKey]: rewrittenPageAssets
1897
+ }
1898
+ };
1899
+ }
1900
+ return chunk;
1901
+ }
1902
+ function parsePageCitationAssets(value) {
1903
+ if (!Array.isArray(value)) return [];
1904
+ return value.flatMap((item) => {
1905
+ if (!isRecord(item)) return [];
1906
+ const pageNum = item.pageNum;
1907
+ const artifactRef = item.artifactRef;
1908
+ const contentType = item.contentType;
1909
+ const source = item.source;
1910
+ if (typeof pageNum !== "number" || !Number.isSafeInteger(pageNum) || pageNum <= 0) return [];
1911
+ if (typeof artifactRef !== "string") return [];
1912
+ if (contentType !== "image/png" && contentType !== "image/jpeg") return [];
1913
+ if (source !== "knowhere-rendered-page-citation-source") return [];
1914
+ return [
1915
+ {
1916
+ pageNum,
1917
+ artifactRef,
1918
+ assetUrl: typeof item.assetUrl === "string" ? item.assetUrl : void 0,
1919
+ contentType,
1920
+ width: typeof item.width === "number" ? item.width : void 0,
1921
+ height: typeof item.height === "number" ? item.height : void 0,
1922
+ source
1923
+ }
1924
+ ];
1925
+ });
1926
+ }
1927
+ function normalizeStorageKeyPrefix(value) {
1928
+ const normalized = normalizeStoragePath(value, { allowDotPrefix: false });
1929
+ if (!normalized) {
1930
+ throw new Error("storageAdapter keyPrefix must be a non-empty relative POSIX path.");
1931
+ }
1932
+ return normalized;
1933
+ }
1934
+ function normalizeResultAssetPath(value) {
1935
+ return normalizeStoragePath(value, { allowDotPrefix: true });
1936
+ }
1937
+ function normalizeStoragePath(value, options) {
1938
+ if (!value) return null;
1939
+ const trimmed = value.trim();
1940
+ if (trimmed.length === 0 || trimmed.includes("\0")) return null;
1941
+ if (trimmed.startsWith("/") || /^[A-Za-z]:/.test(trimmed)) return null;
1942
+ const normalized = options.allowDotPrefix ? trimmed.replaceAll("\\", "/").replace(/^\.\/+/, "") : trimmed.replaceAll("\\", "/");
1943
+ if (normalized.length === 0) return null;
1944
+ const parts = normalized.split("/");
1945
+ if (parts.some((part) => part.length === 0 || part === "." || part === "..")) return null;
1946
+ return parts.join("/");
1947
+ }
1948
+ function refreshStaticChunkCollections(result) {
1949
+ replaceStaticChunkCollection(
1950
+ result,
1951
+ "textChunks",
1952
+ result.chunks.filter((chunk) => chunk.type === "text")
1953
+ );
1954
+ replaceStaticChunkCollection(
1955
+ result,
1956
+ "imageChunks",
1957
+ result.chunks.filter((chunk) => chunk.type === "image")
1958
+ );
1959
+ replaceStaticChunkCollection(
1960
+ result,
1961
+ "tableChunks",
1962
+ result.chunks.filter((chunk) => chunk.type === "table")
1963
+ );
1964
+ replaceStaticChunkCollection(
1965
+ result,
1966
+ "pageChunks",
1967
+ result.chunks.filter((chunk) => chunk.type === "page")
1968
+ );
1969
+ }
1970
+ function replaceStaticChunkCollection(result, property, value) {
1971
+ const descriptor = Object.getOwnPropertyDescriptor(result, property);
1972
+ if (!descriptor || descriptor.get || descriptor.writable !== true) return;
1973
+ Object.defineProperty(result, property, {
1974
+ ...descriptor,
1975
+ value
1976
+ });
1977
+ }
1978
+ function getImageContentType(filePath) {
1979
+ const extension = filePath.split(".").at(-1)?.toLowerCase();
1980
+ if (extension === "jpg" || extension === "jpeg") return "image/jpeg";
1981
+ if (extension === "png") return "image/png";
1982
+ if (extension === "webp") return "image/webp";
1983
+ if (extension === "gif") return "image/gif";
1984
+ if (extension === "svg") return "image/svg+xml";
1985
+ return defaultImageContentType;
1986
+ }
1987
+ function isRecord(value) {
1988
+ return typeof value === "object" && value !== null;
1989
+ }
1990
+
1991
+ // src/storage/parsed-document-storage.ts
1992
+ var parsedStorageKeyPrefix = "parsed";
1993
+ var manifestStoragePath = "manifest/current.json";
1994
+ var indexStoragePath = "index.json";
1995
+ var chunkPagePathPattern = /^chunks\/page-(\d+)\.json$/;
1996
+ async function syncParseResultToParsedDocumentStorage(params) {
1997
+ const documentId = params.documentId ?? params.result.documentId ?? params.result.jobId;
1998
+ const revisionKey = params.revisionKey ?? params.result.jobId;
1999
+ const adapter = createParsedDocumentObjectStorageAdapter({
2000
+ storage: params.storage,
2001
+ documentId,
2002
+ revisionKey
2003
+ });
2004
+ const stored = await storeParseResultAssets(params.result, {
2005
+ adapter,
2006
+ keyPrefix: parsedStorageKeyPrefix,
2007
+ chunkPageSize: params.chunkPageSize,
2008
+ revisionKey
2009
+ });
2010
+ return {
2011
+ result: stored.result,
2012
+ snapshot: stored.snapshot,
2013
+ documentId,
2014
+ revisionKey
2015
+ };
2016
+ }
2017
+ function createParsedDocumentObjectStorageAdapter(params) {
2018
+ return {
2019
+ writeObject: (input) => writeParsedStorageObject(params, input),
2020
+ headObject: (key) => headParsedStorageObject(params, key),
2021
+ getObjectUrl: (key) => getParsedStorageObjectUrl(params, key)
2022
+ };
2023
+ }
2024
+ async function writeParsedStorageObject(params, input) {
2025
+ const storagePath = stripParsedStorageKeyPrefix(input.key);
2026
+ if (storagePath === manifestStoragePath) {
2027
+ const manifest = JSON.parse(
2028
+ Buffer.from(input.body).toString("utf8")
2029
+ );
2030
+ await params.storage.writeManifest({
2031
+ documentId: params.documentId,
2032
+ revisionKey: params.revisionKey,
2033
+ manifest
2034
+ });
2035
+ return { key: input.key };
2036
+ }
2037
+ if (storagePath === indexStoragePath) {
2038
+ return { key: input.key };
2039
+ }
2040
+ const chunkPageMatch = storagePath.match(chunkPagePathPattern);
2041
+ if (chunkPageMatch) {
2042
+ const page = JSON.parse(
2043
+ Buffer.from(input.body).toString("utf8")
2044
+ );
2045
+ await params.storage.writeChunkPage({
2046
+ documentId: params.documentId,
2047
+ revisionKey: params.revisionKey,
2048
+ page
2049
+ });
2050
+ return { key: input.key };
2051
+ }
2052
+ const stored = await params.storage.writeAsset({
2053
+ documentId: params.documentId,
2054
+ revisionKey: params.revisionKey,
2055
+ sourcePath: storagePath,
2056
+ body: input.body,
2057
+ contentType: input.contentType,
2058
+ metadata: input.metadata
2059
+ });
2060
+ return {
2061
+ key: input.key,
2062
+ url: stored.url
2063
+ };
2064
+ }
2065
+ async function headParsedStorageObject(params, key) {
2066
+ const storagePath = stripParsedStorageKeyPrefix(key);
2067
+ if (storagePath === manifestStoragePath || storagePath === indexStoragePath || chunkPagePathPattern.test(storagePath)) {
2068
+ return null;
2069
+ }
2070
+ const url = await params.storage.getAssetUrl({
2071
+ documentId: params.documentId,
2072
+ revisionKey: params.revisionKey,
2073
+ sourcePath: storagePath
2074
+ });
2075
+ return url ? {
2076
+ key,
2077
+ url
2078
+ } : null;
2079
+ }
2080
+ async function getParsedStorageObjectUrl(params, key) {
2081
+ const storagePath = stripParsedStorageKeyPrefix(key);
2082
+ if (storagePath === manifestStoragePath || storagePath === indexStoragePath || chunkPagePathPattern.test(storagePath)) {
2083
+ return null;
2084
+ }
2085
+ return params.storage.getAssetUrl({
2086
+ documentId: params.documentId,
2087
+ revisionKey: params.revisionKey,
2088
+ sourcePath: storagePath
2089
+ });
2090
+ }
2091
+ function stripParsedStorageKeyPrefix(key) {
2092
+ const prefix = `${parsedStorageKeyPrefix}/`;
2093
+ return key.startsWith(prefix) ? key.slice(prefix.length) : key;
2094
+ }
2095
+
1543
2096
  // src/knowledge/local-store.ts
1544
2097
  var import_crypto = require("crypto");
1545
2098
  var import_os = __toESM(require("os"));
@@ -1734,7 +2287,7 @@ function countChunkTypes(result) {
1734
2287
  counts[chunk.type] += 1;
1735
2288
  return counts;
1736
2289
  },
1737
- { text: 0, image: 0, table: 0 }
2290
+ { text: 0, image: 0, table: 0, page: 0 }
1738
2291
  );
1739
2292
  }
1740
2293
  function toLocalKnowledgeDocument(stored) {
@@ -1765,27 +2318,62 @@ function isMissingFileError2(error) {
1765
2318
  // src/knowledge/knowledge.ts
1766
2319
  var DEFAULT_READ_LIMIT = 12;
1767
2320
  var MAX_READ_LIMIT = 40;
2321
+ var DEFAULT_PAGE_SIZE = 20;
2322
+ var MAX_PAGE_SIZE = 200;
1768
2323
  var DEFAULT_GREP_LIMIT = 20;
1769
2324
  var MAX_GREP_LIMIT = 50;
1770
2325
  var DEFAULT_CONTEXT_CHARS = 80;
2326
+ var DEFAULT_REMOTE_SCAN_PAGE_SIZE = 100;
2327
+ var DEFAULT_MAX_SYNC_PAGES = 10;
2328
+ var DEFAULT_MAX_SYNC_ASSETS = 20;
2329
+ var DEFAULT_SYNC_DEADLINE_MS = 8e3;
2330
+ var DEFAULT_GREP_MAX_PAGES = 50;
2331
+ var DEFAULT_GREP_DEADLINE_MS = 8e3;
2332
+ var DEFAULT_OUTLINE_MAX_PAGES = 50;
2333
+ var DEFAULT_OUTLINE_DEADLINE_MS = 8e3;
1771
2334
  var SAFE_LOCAL_DOCUMENT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
1772
2335
  var REMOTE_DOCUMENT_ID_PATTERN = /^doc[_-]/;
1773
2336
  var Knowledge = class _Knowledge {
1774
2337
  client;
1775
2338
  store;
2339
+ cacheDirectory;
2340
+ parsedStorageConfig;
1776
2341
  constructor(client, options) {
1777
2342
  this.client = client;
2343
+ this.cacheDirectory = options?.cacheDirectory;
2344
+ this.parsedStorageConfig = options?.parsedStorage;
1778
2345
  this.store = new LocalKnowledgeStore(options?.cacheDirectory);
1779
2346
  }
1780
2347
  withCacheDirectory(cacheDirectory) {
1781
- return new _Knowledge(this.client, { cacheDirectory });
2348
+ return new _Knowledge(this.client, {
2349
+ cacheDirectory,
2350
+ parsedStorage: this.parsedStorageConfig
2351
+ });
1782
2352
  }
1783
- async parse(params) {
1784
- const result = await this.client.parse(params);
2353
+ withParsedStorage(parsedStorage) {
2354
+ return new _Knowledge(this.client, {
2355
+ cacheDirectory: this.cacheDirectory,
2356
+ parsedStorage
2357
+ });
2358
+ }
2359
+ async parseToLocalCache(params) {
2360
+ const loadedResult = params.storageAdapter ? await this.parseWithoutClientStorageAdapter(params) : await this.client.parse(params);
2361
+ const storedAssets = await storeParseResultAssets(loadedResult, params.storageAdapter);
2362
+ const syncedStorage = await this.syncParseResultToConfiguredStorage(storedAssets.result);
2363
+ const result = syncedStorage.result;
1785
2364
  const document = await this.store.saveResult(result, {
1786
2365
  localDocumentId: params.localDocumentId
1787
2366
  });
1788
- return { document, result };
2367
+ return {
2368
+ document,
2369
+ result,
2370
+ assetUrlsByFilePath: storedAssets.assetUrlsByFilePath,
2371
+ parsedSnapshot: storedAssets.snapshot
2372
+ };
2373
+ }
2374
+ /** @deprecated Use parseToLocalCache for local cache imports or the top-level client.parse for server-safe parsing. */
2375
+ async parse(params) {
2376
+ return this.parseToLocalCache(params);
1789
2377
  }
1790
2378
  async startParse(params) {
1791
2379
  const job = await this.client.startParse(params);
@@ -1816,14 +2404,100 @@ var Knowledge = class _Knowledge {
1816
2404
  results
1817
2405
  };
1818
2406
  }
1819
- async cacheJobResult(params) {
1820
- const result = await this.client.jobs.load(params.jobId, {
2407
+ async loadJobResult(params) {
2408
+ const loadedResult = await this.client.jobs.load(params.jobId, {
1821
2409
  verifyChecksum: params.verifyChecksum
1822
2410
  });
2411
+ const storedAssets = await storeParseResultAssets(loadedResult, params.storageAdapter);
2412
+ const syncedStorage = await this.syncParseResultToConfiguredStorage(storedAssets.result);
2413
+ const result = syncedStorage.result;
2414
+ return {
2415
+ result,
2416
+ assetUrlsByFilePath: storedAssets.assetUrlsByFilePath,
2417
+ parsedSnapshot: syncedStorage.snapshot ?? storedAssets.snapshot
2418
+ };
2419
+ }
2420
+ async importJobResult(params) {
2421
+ const stored = await this.loadJobResult(params);
2422
+ const result = stored.result;
1823
2423
  const document = await this.store.saveResult(result, {
1824
2424
  localDocumentId: params.localDocumentId
1825
2425
  });
1826
- return { document, result };
2426
+ return {
2427
+ document,
2428
+ result,
2429
+ assetUrlsByFilePath: stored.assetUrlsByFilePath,
2430
+ parsedSnapshot: stored.parsedSnapshot
2431
+ };
2432
+ }
2433
+ /** @deprecated Use importJobResult for local cache imports or loadJobResult for server-safe loads. */
2434
+ async cacheJobResult(params) {
2435
+ return this.importJobResult(params);
2436
+ }
2437
+ async syncParsedDocument(params) {
2438
+ const normalized = normalizeDocumentReference(params);
2439
+ const parsedStorageConfig = this.requireParsedStorageConfig();
2440
+ if (normalized.documentId) {
2441
+ return this.syncRemoteDocumentToParsedStorage({
2442
+ documentId: normalized.documentId,
2443
+ revisionKey: params.revisionKey,
2444
+ parsedStorageConfig
2445
+ });
2446
+ }
2447
+ if (normalized.jobId) {
2448
+ const loadedResult = await this.client.jobs.load(normalized.jobId);
2449
+ const synced2 = await syncParseResultToParsedDocumentStorage({
2450
+ result: loadedResult,
2451
+ storage: parsedStorageConfig.storage,
2452
+ documentId: loadedResult.documentId ?? normalized.localDocumentId ?? normalized.jobId,
2453
+ revisionKey: params.revisionKey ?? normalized.jobId,
2454
+ chunkPageSize: parsedStorageConfig.limits?.chunkPageSize
2455
+ });
2456
+ return {
2457
+ documentId: synced2.documentId,
2458
+ revisionKey: synced2.revisionKey,
2459
+ completed: true,
2460
+ manifest: synced2.snapshot?.manifest
2461
+ };
2462
+ }
2463
+ if (!normalized.localDocumentId) {
2464
+ throw new ValidationError("localDocumentId, documentId, or jobId is required");
2465
+ }
2466
+ const { result } = await this.store.loadResult(normalized.localDocumentId);
2467
+ const synced = await syncParseResultToParsedDocumentStorage({
2468
+ result,
2469
+ storage: parsedStorageConfig.storage,
2470
+ documentId: result.documentId ?? normalized.localDocumentId,
2471
+ revisionKey: params.revisionKey ?? result.jobId,
2472
+ chunkPageSize: parsedStorageConfig.limits?.chunkPageSize
2473
+ });
2474
+ return {
2475
+ documentId: synced.documentId,
2476
+ revisionKey: synced.revisionKey,
2477
+ completed: true,
2478
+ manifest: synced.snapshot?.manifest
2479
+ };
2480
+ }
2481
+ async syncParseResultToConfiguredStorage(result) {
2482
+ const parsedStorageConfig = this.parsedStorageConfig;
2483
+ if (!parsedStorageConfig) {
2484
+ return { result };
2485
+ }
2486
+ const synced = await syncParseResultToParsedDocumentStorage({
2487
+ result,
2488
+ storage: parsedStorageConfig.storage,
2489
+ chunkPageSize: parsedStorageConfig.limits?.chunkPageSize
2490
+ });
2491
+ return {
2492
+ result: synced.result,
2493
+ snapshot: synced.snapshot
2494
+ };
2495
+ }
2496
+ async parseWithoutClientStorageAdapter(params) {
2497
+ const parseParams = { ...params };
2498
+ delete parseParams.localDocumentId;
2499
+ delete parseParams.storageAdapter;
2500
+ return this.client.parse(parseParams);
1827
2501
  }
1828
2502
  async cacheDocument(params) {
1829
2503
  if (!params.documentId) {
@@ -1833,7 +2507,7 @@ var Knowledge = class _Knowledge {
1833
2507
  params.documentId
1834
2508
  );
1835
2509
  const jobId = await this.getPublishedDocumentJobId(params.documentId);
1836
- return this.cacheJobResult({
2510
+ return this.importJobResult({
1837
2511
  jobId,
1838
2512
  localDocumentId: params.localDocumentId ?? existing?.localDocumentId ?? createLocalDocumentIdForRemote(params.documentId)
1839
2513
  });
@@ -1870,7 +2544,7 @@ var Knowledge = class _Knowledge {
1870
2544
  };
1871
2545
  }
1872
2546
  try {
1873
- const cached = await this.cacheJobResult({
2547
+ const cached = await this.importJobResult({
1874
2548
  jobId,
1875
2549
  localDocumentId: trackedJob.localDocumentId
1876
2550
  });
@@ -1895,6 +2569,13 @@ var Knowledge = class _Knowledge {
1895
2569
  return this.store.listDocuments();
1896
2570
  }
1897
2571
  async getDocumentOutline(reference) {
2572
+ const normalized = normalizeDocumentReference(reference);
2573
+ if (normalized.documentId) {
2574
+ return this.getRemoteDocumentOutline(normalized.documentId, normalized.revisionKey);
2575
+ }
2576
+ if (normalized.localDocumentId && looksLikeRemoteDocumentId(normalized.localDocumentId) && !await this.store.getDocument(normalized.localDocumentId)) {
2577
+ return this.getRemoteDocumentOutline(normalized.localDocumentId, normalized.revisionKey);
2578
+ }
1898
2579
  const { document, result } = await this.loadReadableResult(reference);
1899
2580
  const chunks = indexChunks(result);
1900
2581
  const sections = buildFlatSections(result, chunks);
@@ -1910,6 +2591,14 @@ var Knowledge = class _Knowledge {
1910
2591
  };
1911
2592
  }
1912
2593
  async readChunks(params) {
2594
+ validateReadParams(params);
2595
+ const normalized = normalizeDocumentReference(params);
2596
+ if (normalized.documentId) {
2597
+ return this.readRemoteDocumentChunks(normalized.documentId, params);
2598
+ }
2599
+ if (normalized.localDocumentId && looksLikeRemoteDocumentId(normalized.localDocumentId) && !await this.store.getDocument(normalized.localDocumentId)) {
2600
+ return this.readRemoteDocumentChunks(normalized.localDocumentId, params);
2601
+ }
1913
2602
  const { document, result } = await this.loadReadableResult(params);
1914
2603
  const limit = clampLimit(params.limit, DEFAULT_READ_LIMIT, MAX_READ_LIMIT);
1915
2604
  const chunks = indexChunks(result).filter((chunk) => matchesReadScope(chunk, params));
@@ -1926,6 +2615,13 @@ var Knowledge = class _Knowledge {
1926
2615
  if (!params.pattern) {
1927
2616
  throw new ValidationError("pattern is required");
1928
2617
  }
2618
+ const normalized = normalizeDocumentReference(params);
2619
+ if (normalized.documentId) {
2620
+ return this.grepRemoteDocumentChunks(normalized.documentId, params);
2621
+ }
2622
+ if (normalized.localDocumentId && looksLikeRemoteDocumentId(normalized.localDocumentId) && !await this.store.getDocument(normalized.localDocumentId)) {
2623
+ return this.grepRemoteDocumentChunks(normalized.localDocumentId, params);
2624
+ }
1929
2625
  const { document, result } = await this.loadReadableResult(params);
1930
2626
  const maxResults = clampLimit(params.maxResults, DEFAULT_GREP_LIMIT, MAX_GREP_LIMIT);
1931
2627
  const contextChars = params.contextChars ?? DEFAULT_CONTEXT_CHARS;
@@ -2005,7 +2701,7 @@ var Knowledge = class _Knowledge {
2005
2701
  });
2006
2702
  }
2007
2703
  if (normalized.jobId) {
2008
- return this.cacheJobResult({
2704
+ return this.importJobResult({
2009
2705
  jobId: normalized.jobId,
2010
2706
  localDocumentId: normalized.localDocumentId
2011
2707
  });
@@ -2025,24 +2721,670 @@ var Knowledge = class _Knowledge {
2025
2721
  }
2026
2722
  throw new Error(`Local Knowhere document not found: ${normalized.localDocumentId}`);
2027
2723
  }
2028
- async findLocalDocumentByRemoteId(documentId) {
2029
- const documents = await this.store.listDocuments();
2030
- return documents.find((document) => document.documentId === documentId);
2724
+ async readRemoteDocumentChunks(documentId, params) {
2725
+ const explicitRevisionKey = params.revisionKey ?? params.jobId;
2726
+ if (explicitRevisionKey) {
2727
+ const stored2 = await this.tryReadChunksFromParsedStorage({
2728
+ documentId,
2729
+ revisionKey: explicitRevisionKey,
2730
+ params
2731
+ });
2732
+ if (stored2) {
2733
+ return stored2;
2734
+ }
2735
+ }
2736
+ if (hasScanReadFilters(params)) {
2737
+ return this.scanRemoteDocumentChunks(documentId, params);
2738
+ }
2739
+ const pageParams = getReadPageParams(params);
2740
+ const remotePage = await this.client.documents.listChunks(documentId, {
2741
+ page: pageParams.page,
2742
+ pageSize: pageParams.pageSize,
2743
+ chunkType: params.chunkType,
2744
+ includeAssetUrls: shouldRequestRemoteAssetUrls(
2745
+ params.assetUrlPolicy,
2746
+ this.parsedStorageConfig
2747
+ )
2748
+ });
2749
+ const revisionKey = getRemoteRevisionKey(remotePage);
2750
+ const stored = await this.tryReadChunksFromParsedStorage({
2751
+ documentId,
2752
+ revisionKey,
2753
+ params
2754
+ });
2755
+ if (stored) {
2756
+ return stored;
2757
+ }
2758
+ const chunks = await this.toRemoteReadChunks({
2759
+ documentId,
2760
+ revisionKey,
2761
+ chunks: remotePage.chunks,
2762
+ assetUrlPolicy: params.assetUrlPolicy
2763
+ });
2764
+ this.scheduleParsedStorageSync(documentId, revisionKey);
2765
+ return {
2766
+ document: createRemoteKnowledgeDocument({
2767
+ documentId,
2768
+ response: remotePage,
2769
+ revisionKey
2770
+ }),
2771
+ chunks,
2772
+ page: remotePage.pagination.page,
2773
+ pageSize: remotePage.pagination.pageSize,
2774
+ totalChunks: remotePage.pagination.total,
2775
+ totalPages: remotePage.pagination.totalPages
2776
+ };
2031
2777
  }
2032
- async getPublishedDocumentJobId(documentId) {
2033
- const response = await this.client.documents.listChunks(documentId, {
2778
+ async scanRemoteDocumentChunks(documentId, params) {
2779
+ const explicitRevisionKey = params.revisionKey ?? params.jobId;
2780
+ if (explicitRevisionKey) {
2781
+ const stored2 = await this.tryReadChunksFromParsedStorage({
2782
+ documentId,
2783
+ revisionKey: explicitRevisionKey,
2784
+ params
2785
+ });
2786
+ if (stored2) {
2787
+ return stored2;
2788
+ }
2789
+ }
2790
+ const limit = clampLimit(params.limit, DEFAULT_READ_LIMIT, MAX_READ_LIMIT);
2791
+ const limits = getParsedStorageLimits(this.parsedStorageConfig?.limits);
2792
+ const firstPage = await this.client.documents.listChunks(documentId, {
2034
2793
  page: 1,
2035
- pageSize: 1
2794
+ pageSize: limits.remotePageSize,
2795
+ chunkType: params.chunkType,
2796
+ includeAssetUrls: shouldRequestRemoteAssetUrls(
2797
+ params.assetUrlPolicy,
2798
+ this.parsedStorageConfig
2799
+ )
2036
2800
  });
2037
- if (!response.jobId) {
2038
- throw new Error(
2039
- `Cannot sync server document ${documentId}: current published job id was not returned.`
2040
- );
2801
+ const revisionKey = getRemoteRevisionKey(firstPage);
2802
+ const stored = await this.tryReadChunksFromParsedStorage({
2803
+ documentId,
2804
+ revisionKey,
2805
+ params
2806
+ });
2807
+ if (stored) {
2808
+ return stored;
2041
2809
  }
2042
- return response.jobId;
2810
+ const selected = [];
2811
+ let scannedPages = 0;
2812
+ let page = 1;
2813
+ let response = firstPage;
2814
+ const startedAt = Date.now();
2815
+ while (true) {
2816
+ scannedPages += 1;
2817
+ selected.push(...response.chunks.filter((chunk) => matchesRemoteReadScope(chunk, params)));
2818
+ if (selected.length >= limit || response.pagination.page >= response.pagination.totalPages || scannedPages >= limits.outlineMaxPages || Date.now() - startedAt >= limits.outlineDeadlineMs) {
2819
+ break;
2820
+ }
2821
+ page += 1;
2822
+ response = await this.client.documents.listChunks(documentId, {
2823
+ page,
2824
+ pageSize: limits.remotePageSize,
2825
+ chunkType: params.chunkType,
2826
+ includeAssetUrls: shouldRequestRemoteAssetUrls(
2827
+ params.assetUrlPolicy,
2828
+ this.parsedStorageConfig
2829
+ )
2830
+ });
2831
+ }
2832
+ const chunks = await this.toRemoteReadChunks({
2833
+ documentId,
2834
+ revisionKey,
2835
+ chunks: selected.slice(0, limit),
2836
+ assetUrlPolicy: params.assetUrlPolicy
2837
+ });
2838
+ this.scheduleParsedStorageSync(documentId, revisionKey);
2839
+ return {
2840
+ document: createRemoteKnowledgeDocument({
2841
+ documentId,
2842
+ response: firstPage,
2843
+ revisionKey
2844
+ }),
2845
+ chunks,
2846
+ nextChunk: selected.length > limit ? toChunkPosition(selected[limit]) : void 0,
2847
+ totalChunks: firstPage.pagination.total,
2848
+ totalPages: firstPage.pagination.totalPages
2849
+ };
2043
2850
  }
2044
- };
2045
- function normalizeDocumentReference(reference) {
2851
+ async grepRemoteDocumentChunks(documentId, params) {
2852
+ const explicitRevisionKey = params.revisionKey ?? params.jobId;
2853
+ if (explicitRevisionKey && !params.continuationCursor) {
2854
+ const stored2 = await this.tryGrepChunksFromParsedStorage({
2855
+ documentId,
2856
+ revisionKey: explicitRevisionKey,
2857
+ params
2858
+ });
2859
+ if (stored2) {
2860
+ return stored2;
2861
+ }
2862
+ }
2863
+ const maxResults = clampLimit(params.maxResults, DEFAULT_GREP_LIMIT, MAX_GREP_LIMIT);
2864
+ const contextChars = params.contextChars ?? DEFAULT_CONTEXT_CHARS;
2865
+ const matcher = createMatcher(params);
2866
+ const limits = getParsedStorageLimits(this.parsedStorageConfig?.limits);
2867
+ const cursor = parseContinuationCursor(params.continuationCursor);
2868
+ const startPage = cursor?.documentId === documentId ? cursor.nextPage : 1;
2869
+ const firstPage = await this.client.documents.listChunks(documentId, {
2870
+ page: startPage,
2871
+ pageSize: limits.remotePageSize,
2872
+ chunkType: params.chunkType,
2873
+ includeAssetUrls: false
2874
+ });
2875
+ const revisionKey = getRemoteRevisionKey(firstPage);
2876
+ const stored = startPage === 1 ? await this.tryGrepChunksFromParsedStorage({
2877
+ documentId,
2878
+ revisionKey,
2879
+ params
2880
+ }) : null;
2881
+ if (stored) {
2882
+ return stored;
2883
+ }
2884
+ const matches = [];
2885
+ let scannedChunks = 0;
2886
+ let scannedPages = 0;
2887
+ let response = firstPage;
2888
+ let page = startPage;
2889
+ const startedAt = Date.now();
2890
+ while (true) {
2891
+ scannedPages += 1;
2892
+ for (const [chunkIndex, chunk] of response.chunks.entries()) {
2893
+ if (response.pagination.page === startPage && cursor?.nextChunkIndex !== void 0 && chunkIndex < cursor.nextChunkIndex) {
2894
+ continue;
2895
+ }
2896
+ if (!matchesGrepScope(toIndexedRemoteChunk(chunk), params)) {
2897
+ continue;
2898
+ }
2899
+ scannedChunks += 1;
2900
+ const chunkMatches = matcher(chunk.content ?? "");
2901
+ for (const [matchIndex, match] of chunkMatches.entries()) {
2902
+ if (response.pagination.page === startPage && cursor?.nextChunkIndex === chunkIndex && cursor.nextMatchIndex !== void 0 && matchIndex < cursor.nextMatchIndex) {
2903
+ continue;
2904
+ }
2905
+ matches.push({
2906
+ position: toChunkPosition(chunk),
2907
+ chunkId: chunk.chunkId,
2908
+ chunkType: chunk.chunkType,
2909
+ sectionPath: chunk.sectionPath ?? "",
2910
+ sourceChunkPath: chunk.sourceChunkPath ?? chunk.sectionPath ?? "",
2911
+ filePath: chunk.filePath ?? void 0,
2912
+ startOffset: match.startOffset,
2913
+ endOffset: match.endOffset,
2914
+ snippet: buildSnippet(
2915
+ chunk.content ?? "",
2916
+ match.startOffset,
2917
+ match.endOffset,
2918
+ contextChars
2919
+ )
2920
+ });
2921
+ if (matches.length >= maxResults) {
2922
+ const continuationCursor = createGrepContinuationCursor({
2923
+ documentId,
2924
+ revisionKey,
2925
+ response,
2926
+ chunkIndex,
2927
+ matchIndex,
2928
+ chunkMatchCount: chunkMatches.length
2929
+ });
2930
+ this.scheduleParsedStorageSync(documentId, revisionKey);
2931
+ return {
2932
+ document: createRemoteKnowledgeDocument({
2933
+ documentId,
2934
+ response: firstPage,
2935
+ revisionKey
2936
+ }),
2937
+ matches,
2938
+ scannedChunks,
2939
+ truncated: continuationCursor !== void 0,
2940
+ continuationCursor
2941
+ };
2942
+ }
2943
+ }
2944
+ }
2945
+ const shouldStop = response.pagination.page >= response.pagination.totalPages || scannedPages >= limits.grepMaxPages || Date.now() - startedAt >= limits.grepDeadlineMs;
2946
+ if (shouldStop) {
2947
+ const truncated = response.pagination.page < response.pagination.totalPages;
2948
+ this.scheduleParsedStorageSync(documentId, revisionKey);
2949
+ return {
2950
+ document: createRemoteKnowledgeDocument({
2951
+ documentId,
2952
+ response: firstPage,
2953
+ revisionKey
2954
+ }),
2955
+ matches,
2956
+ scannedChunks,
2957
+ truncated,
2958
+ continuationCursor: truncated ? createContinuationCursor({
2959
+ documentId,
2960
+ revisionKey,
2961
+ nextPage: response.pagination.page + 1
2962
+ }) : void 0
2963
+ };
2964
+ }
2965
+ page += 1;
2966
+ response = await this.client.documents.listChunks(documentId, {
2967
+ page,
2968
+ pageSize: limits.remotePageSize,
2969
+ chunkType: params.chunkType,
2970
+ includeAssetUrls: false
2971
+ });
2972
+ }
2973
+ }
2974
+ async getRemoteDocumentOutline(documentId, revisionKeyOverride) {
2975
+ if (revisionKeyOverride) {
2976
+ const stored2 = await this.tryReadOutlineFromParsedStorage(documentId, revisionKeyOverride);
2977
+ if (stored2) {
2978
+ return stored2;
2979
+ }
2980
+ }
2981
+ const limits = getParsedStorageLimits(this.parsedStorageConfig?.limits);
2982
+ const firstPage = await this.client.documents.listChunks(documentId, {
2983
+ page: 1,
2984
+ pageSize: limits.remotePageSize,
2985
+ includeAssetUrls: false
2986
+ });
2987
+ const revisionKey = getRemoteRevisionKey(firstPage);
2988
+ const stored = await this.tryReadOutlineFromParsedStorage(documentId, revisionKey);
2989
+ if (stored) {
2990
+ return stored;
2991
+ }
2992
+ const chunks = [];
2993
+ let page = 1;
2994
+ let response = firstPage;
2995
+ let scannedPages = 0;
2996
+ const startedAt = Date.now();
2997
+ while (true) {
2998
+ scannedPages += 1;
2999
+ chunks.push(...response.chunks.map(toIndexedRemoteChunk));
3000
+ if (response.pagination.page >= response.pagination.totalPages || scannedPages >= limits.outlineMaxPages || Date.now() - startedAt >= limits.outlineDeadlineMs) {
3001
+ break;
3002
+ }
3003
+ page += 1;
3004
+ response = await this.client.documents.listChunks(documentId, {
3005
+ page,
3006
+ pageSize: limits.remotePageSize,
3007
+ includeAssetUrls: false
3008
+ });
3009
+ }
3010
+ const truncated = response.pagination.page < response.pagination.totalPages;
3011
+ const sections = buildFlatSectionsFromIndexedChunks(chunks);
3012
+ this.scheduleParsedStorageSync(documentId, revisionKey);
3013
+ return {
3014
+ document: createRemoteKnowledgeDocument({
3015
+ documentId,
3016
+ response: firstPage,
3017
+ revisionKey,
3018
+ typeCounts: countIndexedTypes(chunks)
3019
+ }),
3020
+ totalChunks: firstPage.pagination.total,
3021
+ typeCounts: countIndexedTypes(chunks),
3022
+ sections,
3023
+ sectionTree: nestSections(sections),
3024
+ truncated,
3025
+ continuationCursor: truncated ? createContinuationCursor({
3026
+ documentId,
3027
+ revisionKey,
3028
+ nextPage: response.pagination.page + 1
3029
+ }) : void 0
3030
+ };
3031
+ }
3032
+ async tryReadChunksFromParsedStorage(params) {
3033
+ const parsedStorageConfig = this.parsedStorageConfig;
3034
+ if (!parsedStorageConfig) {
3035
+ return null;
3036
+ }
3037
+ const manifest = await this.readFreshParsedStorageManifest(
3038
+ params.documentId,
3039
+ params.revisionKey
3040
+ );
3041
+ if (!manifest) {
3042
+ return null;
3043
+ }
3044
+ const pageParams = getReadPageParams(params.params);
3045
+ const indexedChunks = await this.readStoredSnapshotChunks({
3046
+ documentId: params.documentId,
3047
+ revisionKey: params.revisionKey,
3048
+ manifest,
3049
+ chunkType: params.params.chunkType
3050
+ });
3051
+ if (!indexedChunks) {
3052
+ return null;
3053
+ }
3054
+ const scopedChunks = indexedChunks.filter((chunk) => matchesReadScope(chunk, params.params));
3055
+ const selected = hasScanReadFilters(params.params) ? selectReadWindow(
3056
+ scopedChunks,
3057
+ params.params,
3058
+ clampLimit(params.params.limit, DEFAULT_READ_LIMIT, MAX_READ_LIMIT)
3059
+ ) : scopedChunks.slice(
3060
+ (pageParams.page - 1) * pageParams.pageSize,
3061
+ pageParams.page * pageParams.pageSize
3062
+ );
3063
+ const visibleChunks = applyAssetUrlPolicyToReadChunks(
3064
+ selected.map(toReadChunk),
3065
+ params.params.assetUrlPolicy
3066
+ );
3067
+ const totalChunks = scopedChunks.length;
3068
+ return {
3069
+ document: createStoredKnowledgeDocument({
3070
+ documentId: params.documentId,
3071
+ manifest,
3072
+ revisionKey: params.revisionKey,
3073
+ typeCounts: countIndexedTypes(indexedChunks)
3074
+ }),
3075
+ chunks: visibleChunks,
3076
+ page: hasScanReadFilters(params.params) ? void 0 : pageParams.page,
3077
+ pageSize: hasScanReadFilters(params.params) ? void 0 : pageParams.pageSize,
3078
+ totalChunks,
3079
+ totalPages: Math.max(1, Math.ceil(totalChunks / pageParams.pageSize)),
3080
+ nextChunk: getNextChunkPosition(scopedChunks, selected)
3081
+ };
3082
+ }
3083
+ async tryGrepChunksFromParsedStorage(params) {
3084
+ const parsedStorageConfig = this.parsedStorageConfig;
3085
+ if (!parsedStorageConfig) {
3086
+ return null;
3087
+ }
3088
+ const manifest = await this.readFreshParsedStorageManifest(
3089
+ params.documentId,
3090
+ params.revisionKey
3091
+ );
3092
+ if (!manifest) {
3093
+ return null;
3094
+ }
3095
+ const indexedChunks = await this.readStoredSnapshotChunks({
3096
+ documentId: params.documentId,
3097
+ revisionKey: params.revisionKey,
3098
+ manifest,
3099
+ chunkType: params.params.chunkType
3100
+ });
3101
+ if (!indexedChunks) {
3102
+ return null;
3103
+ }
3104
+ const maxResults = clampLimit(params.params.maxResults, DEFAULT_GREP_LIMIT, MAX_GREP_LIMIT);
3105
+ const contextChars = params.params.contextChars ?? DEFAULT_CONTEXT_CHARS;
3106
+ const matcher = createMatcher(params.params);
3107
+ const scopedChunks = indexedChunks.filter((chunk) => matchesGrepScope(chunk, params.params));
3108
+ const matches = [];
3109
+ let scannedChunks = 0;
3110
+ for (const chunk of scopedChunks) {
3111
+ scannedChunks += 1;
3112
+ for (const match of matcher(chunk.content)) {
3113
+ matches.push({
3114
+ position: chunk.position,
3115
+ chunkId: chunk.chunkId,
3116
+ chunkType: chunk.chunkType,
3117
+ sectionPath: chunk.sectionPath,
3118
+ sourceChunkPath: chunk.sourceChunkPath,
3119
+ filePath: chunk.filePath,
3120
+ startOffset: match.startOffset,
3121
+ endOffset: match.endOffset,
3122
+ snippet: buildSnippet(chunk.content, match.startOffset, match.endOffset, contextChars)
3123
+ });
3124
+ if (matches.length >= maxResults) {
3125
+ return {
3126
+ document: createStoredKnowledgeDocument({
3127
+ documentId: params.documentId,
3128
+ manifest,
3129
+ revisionKey: params.revisionKey,
3130
+ typeCounts: countIndexedTypes(indexedChunks)
3131
+ }),
3132
+ matches,
3133
+ scannedChunks,
3134
+ truncated: true
3135
+ };
3136
+ }
3137
+ }
3138
+ }
3139
+ return {
3140
+ document: createStoredKnowledgeDocument({
3141
+ documentId: params.documentId,
3142
+ manifest,
3143
+ revisionKey: params.revisionKey,
3144
+ typeCounts: countIndexedTypes(indexedChunks)
3145
+ }),
3146
+ matches,
3147
+ scannedChunks,
3148
+ truncated: false
3149
+ };
3150
+ }
3151
+ async tryReadOutlineFromParsedStorage(documentId, revisionKey) {
3152
+ const manifest = await this.readFreshParsedStorageManifest(documentId, revisionKey);
3153
+ if (!manifest) {
3154
+ return null;
3155
+ }
3156
+ const indexedChunks = await this.readStoredSnapshotChunks({
3157
+ documentId,
3158
+ revisionKey,
3159
+ manifest
3160
+ });
3161
+ if (!indexedChunks) {
3162
+ return null;
3163
+ }
3164
+ const sections = buildFlatSectionsFromIndexedChunks(indexedChunks);
3165
+ const typeCounts = countIndexedTypes(indexedChunks);
3166
+ return {
3167
+ document: createStoredKnowledgeDocument({
3168
+ documentId,
3169
+ manifest,
3170
+ revisionKey,
3171
+ typeCounts
3172
+ }),
3173
+ totalChunks: manifest.totalChunks,
3174
+ typeCounts,
3175
+ sections,
3176
+ sectionTree: nestSections(sections),
3177
+ truncated: false
3178
+ };
3179
+ }
3180
+ async readFreshParsedStorageManifest(documentId, revisionKey) {
3181
+ const parsedStorageConfig = this.parsedStorageConfig;
3182
+ if (!parsedStorageConfig) {
3183
+ return null;
3184
+ }
3185
+ const manifest = await parsedStorageConfig.storage.readManifest({
3186
+ documentId,
3187
+ revisionKey
3188
+ });
3189
+ if (!manifest || !isFreshManifest(manifest, revisionKey)) {
3190
+ return null;
3191
+ }
3192
+ return manifest;
3193
+ }
3194
+ async readStoredSnapshotChunks(params) {
3195
+ const parsedStorageConfig = this.parsedStorageConfig;
3196
+ if (!parsedStorageConfig) {
3197
+ return null;
3198
+ }
3199
+ const chunks = [];
3200
+ for (const pageReference of params.manifest.chunkPages) {
3201
+ const page = await parsedStorageConfig.storage.readChunkPage({
3202
+ documentId: params.documentId,
3203
+ revisionKey: params.revisionKey,
3204
+ page: pageReference.page,
3205
+ chunkType: params.chunkType
3206
+ });
3207
+ if (!page) {
3208
+ return null;
3209
+ }
3210
+ chunks.push(
3211
+ ...page.chunks.filter((chunk) => !params.chunkType || chunk.chunkType === params.chunkType).map(toIndexedSnapshotChunk)
3212
+ );
3213
+ }
3214
+ return chunks.sort((left, right) => left.position - right.position);
3215
+ }
3216
+ async toRemoteReadChunks(params) {
3217
+ const readChunks = params.chunks.map((chunk) => toRemoteReadChunk(chunk));
3218
+ if (params.assetUrlPolicy === "durable") {
3219
+ return this.hardenReadChunkAssetUrls({
3220
+ documentId: params.documentId,
3221
+ revisionKey: params.revisionKey,
3222
+ chunks: readChunks
3223
+ });
3224
+ }
3225
+ return applyAssetUrlPolicyToReadChunks(readChunks, params.assetUrlPolicy);
3226
+ }
3227
+ async hardenReadChunkAssetUrls(params) {
3228
+ const parsedStorageConfig = this.parsedStorageConfig;
3229
+ if (!parsedStorageConfig) {
3230
+ return applyAssetUrlPolicyToReadChunks(params.chunks, "none");
3231
+ }
3232
+ const hardenedChunks = [];
3233
+ for (const chunk of params.chunks) {
3234
+ hardenedChunks.push(
3235
+ await hardenReadChunkAssetUrls({
3236
+ storageConfig: parsedStorageConfig,
3237
+ documentId: params.documentId,
3238
+ revisionKey: params.revisionKey,
3239
+ chunk
3240
+ })
3241
+ );
3242
+ }
3243
+ return hardenedChunks;
3244
+ }
3245
+ scheduleParsedStorageSync(documentId, revisionKey) {
3246
+ const parsedStorageConfig = this.parsedStorageConfig;
3247
+ if (!parsedStorageConfig) {
3248
+ return;
3249
+ }
3250
+ const scheduler = parsedStorageConfig.scheduler ?? defaultParsedStorageScheduler;
3251
+ const task = async () => {
3252
+ try {
3253
+ await this.syncRemoteDocumentToParsedStorage({
3254
+ documentId,
3255
+ revisionKey,
3256
+ parsedStorageConfig
3257
+ });
3258
+ } catch (error) {
3259
+ await parsedStorageConfig.storage.writeSyncProgress({
3260
+ documentId,
3261
+ revisionKey,
3262
+ nextChunkPage: 1,
3263
+ nextAssetIndex: 0,
3264
+ status: "failed",
3265
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3266
+ error: error instanceof Error ? error.message : String(error)
3267
+ });
3268
+ }
3269
+ };
3270
+ void Promise.resolve(scheduler.schedule(task)).catch(() => void 0);
3271
+ }
3272
+ async syncRemoteDocumentToParsedStorage(params) {
3273
+ const limits = getParsedStorageLimits(params.parsedStorageConfig.limits);
3274
+ const existingProgress = params.revisionKey ? await params.parsedStorageConfig.storage.readSyncProgress({
3275
+ documentId: params.documentId,
3276
+ revisionKey: params.revisionKey
3277
+ }) : null;
3278
+ let nextPage = existingProgress?.status === "running" ? existingProgress.nextChunkPage : 1;
3279
+ let response = await this.client.documents.listChunks(params.documentId, {
3280
+ page: nextPage,
3281
+ pageSize: limits.remotePageSize,
3282
+ includeAssetUrls: true
3283
+ });
3284
+ const revisionKey = params.revisionKey ?? getRemoteRevisionKey(response);
3285
+ if (params.revisionKey && getRemoteRevisionKey(response) !== params.revisionKey) {
3286
+ response = await this.client.documents.listChunks(params.documentId, {
3287
+ page: 1,
3288
+ pageSize: limits.remotePageSize,
3289
+ includeAssetUrls: true
3290
+ });
3291
+ nextPage = 1;
3292
+ }
3293
+ const totalPages = response.pagination.totalPages;
3294
+ const chunkPages = [];
3295
+ let syncedPages = 0;
3296
+ const startedAt = Date.now();
3297
+ while (nextPage <= totalPages) {
3298
+ if (syncedPages > 0) {
3299
+ response = await this.client.documents.listChunks(params.documentId, {
3300
+ page: nextPage,
3301
+ pageSize: limits.remotePageSize,
3302
+ includeAssetUrls: true
3303
+ });
3304
+ }
3305
+ const readChunks = await this.hardenReadChunkAssetUrls({
3306
+ documentId: params.documentId,
3307
+ revisionKey,
3308
+ chunks: response.chunks.map(toRemoteReadChunk)
3309
+ });
3310
+ const page = toSnapshotChunkPageFromReadChunks({
3311
+ response,
3312
+ revisionKey,
3313
+ chunks: readChunks
3314
+ });
3315
+ await params.parsedStorageConfig.storage.writeChunkPage({
3316
+ documentId: params.documentId,
3317
+ revisionKey,
3318
+ page
3319
+ });
3320
+ chunkPages.push(page);
3321
+ nextPage += 1;
3322
+ syncedPages += 1;
3323
+ await params.parsedStorageConfig.storage.writeSyncProgress({
3324
+ documentId: params.documentId,
3325
+ revisionKey,
3326
+ nextChunkPage: nextPage,
3327
+ nextAssetIndex: 0,
3328
+ status: "running",
3329
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3330
+ });
3331
+ if (syncedPages >= limits.maxPagesPerSync || Date.now() - startedAt >= limits.syncDeadlineMs) {
3332
+ return {
3333
+ documentId: params.documentId,
3334
+ revisionKey,
3335
+ completed: false
3336
+ };
3337
+ }
3338
+ }
3339
+ const manifest = createRemoteSnapshotManifest({
3340
+ response,
3341
+ revisionKey,
3342
+ chunkPages
3343
+ });
3344
+ await params.parsedStorageConfig.storage.writeManifest({
3345
+ documentId: params.documentId,
3346
+ revisionKey,
3347
+ manifest
3348
+ });
3349
+ await params.parsedStorageConfig.storage.writeSyncProgress({
3350
+ documentId: params.documentId,
3351
+ revisionKey,
3352
+ nextChunkPage: totalPages + 1,
3353
+ nextAssetIndex: 0,
3354
+ status: "completed",
3355
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3356
+ });
3357
+ return {
3358
+ documentId: params.documentId,
3359
+ revisionKey,
3360
+ completed: true,
3361
+ manifest
3362
+ };
3363
+ }
3364
+ requireParsedStorageConfig() {
3365
+ if (!this.parsedStorageConfig) {
3366
+ throw new ValidationError("parsed document storage is not configured");
3367
+ }
3368
+ return this.parsedStorageConfig;
3369
+ }
3370
+ async findLocalDocumentByRemoteId(documentId) {
3371
+ const documents = await this.store.listDocuments();
3372
+ return documents.find((document) => document.documentId === documentId);
3373
+ }
3374
+ async getPublishedDocumentJobId(documentId) {
3375
+ const response = await this.client.documents.listChunks(documentId, {
3376
+ page: 1,
3377
+ pageSize: 1
3378
+ });
3379
+ if (!response.jobId) {
3380
+ throw new Error(
3381
+ `Cannot sync server document ${documentId}: current published job id was not returned.`
3382
+ );
3383
+ }
3384
+ return response.jobId;
3385
+ }
3386
+ };
3387
+ function normalizeDocumentReference(reference) {
2046
3388
  if (typeof reference === "string") {
2047
3389
  return { localDocumentId: reference };
2048
3390
  }
@@ -2058,22 +3400,525 @@ function createLocalDocumentIdForRemote(documentId) {
2058
3400
  const hash = (0, import_crypto2.createHash)("sha256").update(documentId).digest("hex").slice(0, 16);
2059
3401
  return `remote_${hash}`;
2060
3402
  }
3403
+ var defaultParsedStorageScheduler = {
3404
+ schedule(task) {
3405
+ void task();
3406
+ }
3407
+ };
3408
+ function getParsedStorageLimits(limits) {
3409
+ return {
3410
+ chunkPageSize: clampLimit(limits?.chunkPageSize, DEFAULT_REMOTE_SCAN_PAGE_SIZE, MAX_PAGE_SIZE),
3411
+ remotePageSize: clampLimit(
3412
+ limits?.remotePageSize,
3413
+ DEFAULT_REMOTE_SCAN_PAGE_SIZE,
3414
+ MAX_PAGE_SIZE
3415
+ ),
3416
+ maxPagesPerSync: clampLimit(limits?.maxPagesPerSync, DEFAULT_MAX_SYNC_PAGES, 1e3),
3417
+ maxAssetsPerSync: clampLimit(limits?.maxAssetsPerSync, DEFAULT_MAX_SYNC_ASSETS, 1e3),
3418
+ syncDeadlineMs: clampLimit(limits?.syncDeadlineMs, DEFAULT_SYNC_DEADLINE_MS, 6e4),
3419
+ grepMaxPages: clampLimit(limits?.grepMaxPages, DEFAULT_GREP_MAX_PAGES, 1e3),
3420
+ grepDeadlineMs: clampLimit(limits?.grepDeadlineMs, DEFAULT_GREP_DEADLINE_MS, 6e4),
3421
+ outlineMaxPages: clampLimit(limits?.outlineMaxPages, DEFAULT_OUTLINE_MAX_PAGES, 1e3),
3422
+ outlineDeadlineMs: clampLimit(limits?.outlineDeadlineMs, DEFAULT_OUTLINE_DEADLINE_MS, 6e4)
3423
+ };
3424
+ }
3425
+ function validateReadParams(params) {
3426
+ const usesPagedDisplay = params.page !== void 0 || params.pageSize !== void 0 || params.assetUrlPolicy !== void 0;
3427
+ if (!usesPagedDisplay) {
3428
+ return;
3429
+ }
3430
+ if (params.sectionPath !== void 0 || params.startChunk !== void 0 || params.endChunk !== void 0 || params.chunkId !== void 0) {
3431
+ throw new ValidationError(
3432
+ "page, pageSize, and assetUrlPolicy cannot be combined with sectionPath, startChunk, endChunk, or chunkId"
3433
+ );
3434
+ }
3435
+ }
3436
+ function hasScanReadFilters(params) {
3437
+ return params.sectionPath !== void 0 || params.startChunk !== void 0 || params.endChunk !== void 0 || params.chunkId !== void 0;
3438
+ }
3439
+ function getReadPageParams(params) {
3440
+ return {
3441
+ page: clampLimit(params.page, 1, Number.MAX_SAFE_INTEGER),
3442
+ pageSize: clampLimit(params.pageSize ?? params.limit, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE)
3443
+ };
3444
+ }
3445
+ function shouldRequestRemoteAssetUrls(assetUrlPolicy, parsedStorageConfig) {
3446
+ return assetUrlPolicy === "durable" && parsedStorageConfig !== void 0;
3447
+ }
3448
+ function getRemoteRevisionKey(response) {
3449
+ const revisionKey = response.jobResultId ?? response.jobId;
3450
+ if (!revisionKey) {
3451
+ throw new Error(
3452
+ `Cannot read server document ${response.documentId}: current published revision was not returned.`
3453
+ );
3454
+ }
3455
+ return revisionKey;
3456
+ }
3457
+ function createRemoteKnowledgeDocument(params) {
3458
+ return {
3459
+ localDocumentId: params.documentId,
3460
+ jobId: params.response.jobId ?? params.revisionKey,
3461
+ documentId: params.documentId,
3462
+ namespace: params.response.namespace,
3463
+ sourceFileName: params.documentId,
3464
+ chunkCount: params.response.pagination.total,
3465
+ typeCounts: params.typeCounts ?? countRemoteChunkTypes(params.response.chunks),
3466
+ resultDirectoryPath: `remote:${params.documentId}`,
3467
+ createdAt: /* @__PURE__ */ new Date(0),
3468
+ updatedAt: /* @__PURE__ */ new Date(0)
3469
+ };
3470
+ }
3471
+ function createStoredKnowledgeDocument(params) {
3472
+ return {
3473
+ localDocumentId: params.documentId,
3474
+ jobId: params.manifest.jobId || params.revisionKey,
3475
+ documentId: params.manifest.documentId ?? params.documentId,
3476
+ namespace: params.manifest.namespace,
3477
+ sourceFileName: params.manifest.sourceFileName,
3478
+ chunkCount: params.manifest.totalChunks,
3479
+ typeCounts: params.typeCounts,
3480
+ resultDirectoryPath: `parsed-storage:${params.documentId}`,
3481
+ createdAt: new Date(params.manifest.createdAt),
3482
+ updatedAt: new Date(params.manifest.createdAt)
3483
+ };
3484
+ }
3485
+ function isFreshManifest(manifest, revisionKey) {
3486
+ return (manifest.revisionKey ?? manifest.jobId) === revisionKey;
3487
+ }
3488
+ function toRemoteReadChunk(chunk) {
3489
+ const metadata = cloneMetadata(chunk.metadata);
3490
+ return {
3491
+ position: toChunkPosition(chunk),
3492
+ chunkId: chunk.chunkId,
3493
+ chunkType: chunk.chunkType,
3494
+ contentSource: chunk.contentSource ?? void 0,
3495
+ content: chunk.content ?? "",
3496
+ readableContent: getReadableRemoteChunkContent(chunk, metadata),
3497
+ sectionPath: chunk.sectionPath ?? "",
3498
+ sourceChunkPath: chunk.sourceChunkPath ?? chunk.sectionPath ?? "",
3499
+ filePath: chunk.filePath ?? void 0,
3500
+ assetUrl: chunk.assetUrl ?? void 0,
3501
+ pageNumbers: getChunkPageNumbers(metadata),
3502
+ metadata
3503
+ };
3504
+ }
3505
+ function getReadableRemoteChunkContent(chunk, metadata) {
3506
+ if (chunk.chunkType !== "page") {
3507
+ return chunk.content ?? "";
3508
+ }
3509
+ const summary = metadata.summary;
3510
+ return typeof summary === "string" && summary.trim().length > 0 ? summary : chunk.content ?? "";
3511
+ }
3512
+ function toIndexedRemoteChunk(chunk) {
3513
+ const readChunk = toRemoteReadChunk(chunk);
3514
+ return toIndexedReadChunk(readChunk);
3515
+ }
3516
+ function toIndexedSnapshotChunk(chunk) {
3517
+ return toIndexedReadChunk({
3518
+ position: chunk.sortOrder,
3519
+ chunkId: chunk.chunkId,
3520
+ chunkType: chunk.chunkType,
3521
+ contentSource: chunk.contentSource,
3522
+ content: chunk.content,
3523
+ readableContent: chunk.chunkType === "page" && typeof chunk.metadata.summary === "string" && chunk.metadata.summary.trim().length > 0 ? chunk.metadata.summary : chunk.content,
3524
+ sectionPath: chunk.sectionPath ?? "",
3525
+ sourceChunkPath: chunk.sourceChunkPath,
3526
+ filePath: chunk.filePath,
3527
+ assetUrl: chunk.assetUrl,
3528
+ pageNumbers: getChunkPageNumbers(chunk.metadata),
3529
+ metadata: cloneMetadata(chunk.metadata)
3530
+ });
3531
+ }
3532
+ function toIndexedReadChunk(chunk) {
3533
+ return {
3534
+ source: createSyntheticChunk(chunk),
3535
+ position: chunk.position,
3536
+ chunkId: chunk.chunkId,
3537
+ chunkType: chunk.chunkType,
3538
+ contentSource: chunk.contentSource,
3539
+ content: chunk.content,
3540
+ readableContent: chunk.readableContent,
3541
+ sectionPath: chunk.sectionPath,
3542
+ sourceChunkPath: chunk.sourceChunkPath,
3543
+ filePath: chunk.filePath,
3544
+ assetUrl: chunk.assetUrl,
3545
+ pageNumbers: chunk.pageNumbers,
3546
+ metadata: chunk.metadata
3547
+ };
3548
+ }
3549
+ function createSyntheticChunk(chunk) {
3550
+ return {
3551
+ chunkId: chunk.chunkId,
3552
+ type: chunk.chunkType,
3553
+ contentSource: chunk.contentSource,
3554
+ content: chunk.content,
3555
+ path: chunk.sourceChunkPath,
3556
+ metadata: chunk.metadata
3557
+ };
3558
+ }
3559
+ function toChunkPosition(chunk) {
3560
+ return chunk ? chunk.sortOrder + 1 : 1;
3561
+ }
3562
+ function buildFlatSectionsFromIndexedChunks(chunks) {
3563
+ const byPath = /* @__PURE__ */ new Map();
3564
+ for (const chunk of chunks) {
3565
+ const path4 = chunk.sectionPath || chunk.sourceChunkPath;
3566
+ const existing = byPath.get(path4);
3567
+ if (existing) {
3568
+ addChunkToSection(existing, chunk);
3569
+ } else {
3570
+ byPath.set(path4, createSectionFromChunk(path4, chunk));
3571
+ }
3572
+ }
3573
+ return [...byPath.values()].sort(compareSections);
3574
+ }
3575
+ function matchesRemoteReadScope(chunk, params) {
3576
+ return matchesReadScope(toIndexedRemoteChunk(chunk), params);
3577
+ }
3578
+ function getNextChunkPosition(chunks, selected) {
3579
+ const lastSelected = selected[selected.length - 1];
3580
+ if (!lastSelected || selected.length >= chunks.length) {
3581
+ return void 0;
3582
+ }
3583
+ return chunks[chunks.indexOf(lastSelected) + 1]?.position;
3584
+ }
3585
+ function applyAssetUrlPolicyToReadChunks(chunks, assetUrlPolicy) {
3586
+ if (assetUrlPolicy !== "none") {
3587
+ return chunks.map((chunk) => ({
3588
+ ...chunk,
3589
+ metadata: cloneMetadata(chunk.metadata)
3590
+ }));
3591
+ }
3592
+ return chunks.map((chunk) => ({
3593
+ ...chunk,
3594
+ assetUrl: void 0,
3595
+ metadata: removeAssetUrlsFromMetadata(chunk.metadata)
3596
+ }));
3597
+ }
3598
+ async function hardenReadChunkAssetUrls(params) {
3599
+ const metadata = cloneMetadata(params.chunk.metadata);
3600
+ const assetUrl = await hardenPrimaryAssetUrl(params, metadata);
3601
+ const pageAssets = await hardenPageAssetUrls(params, metadata);
3602
+ if (pageAssets) {
3603
+ metadata.pageAssets = pageAssets;
3604
+ }
3605
+ return {
3606
+ ...params.chunk,
3607
+ assetUrl,
3608
+ metadata
3609
+ };
3610
+ }
3611
+ async function hardenPrimaryAssetUrl(params, metadata) {
3612
+ if (!params.chunk.assetUrl || !params.chunk.filePath) {
3613
+ return void 0;
3614
+ }
3615
+ return writeDurableAssetUrl({
3616
+ storageConfig: params.storageConfig,
3617
+ documentId: params.documentId,
3618
+ revisionKey: params.revisionKey,
3619
+ sourcePath: params.chunk.filePath,
3620
+ sourceUrl: params.chunk.assetUrl,
3621
+ fallbackContentType: inferContentTypeFromPath(params.chunk.filePath),
3622
+ metadata: {
3623
+ chunkId: params.chunk.chunkId,
3624
+ chunkType: params.chunk.chunkType,
3625
+ sourcePath: params.chunk.filePath,
3626
+ ...stringifyFlatMetadata(metadata)
3627
+ }
3628
+ });
3629
+ }
3630
+ async function hardenPageAssetUrls(params, metadata) {
3631
+ const pageAssets = toUnknownArray(metadata.pageAssets);
3632
+ if (!pageAssets) {
3633
+ return void 0;
3634
+ }
3635
+ const hardenedAssets = [];
3636
+ for (const pageAsset of pageAssets) {
3637
+ if (!isRecord2(pageAsset)) {
3638
+ hardenedAssets.push(pageAsset);
3639
+ continue;
3640
+ }
3641
+ const artifactRef = pageAsset.artifactRef;
3642
+ const sourceUrl = pageAsset.assetUrl;
3643
+ if (typeof artifactRef !== "string" || typeof sourceUrl !== "string") {
3644
+ hardenedAssets.push(removeAssetUrlField(pageAsset));
3645
+ continue;
3646
+ }
3647
+ const durableUrl = await writeDurableAssetUrl({
3648
+ storageConfig: params.storageConfig,
3649
+ documentId: params.documentId,
3650
+ revisionKey: params.revisionKey,
3651
+ sourcePath: artifactRef,
3652
+ sourceUrl,
3653
+ fallbackContentType: typeof pageAsset.contentType === "string" ? pageAsset.contentType : inferContentTypeFromPath(artifactRef),
3654
+ metadata: {
3655
+ chunkId: params.chunk.chunkId,
3656
+ chunkType: params.chunk.chunkType,
3657
+ sourcePath: artifactRef
3658
+ }
3659
+ });
3660
+ const rest = removeAssetUrlField(pageAsset);
3661
+ hardenedAssets.push(durableUrl ? { ...rest, assetUrl: durableUrl } : rest);
3662
+ }
3663
+ return hardenedAssets;
3664
+ }
3665
+ async function writeDurableAssetUrl(params) {
3666
+ const existingUrl = await params.storageConfig.storage.getAssetUrl({
3667
+ documentId: params.documentId,
3668
+ revisionKey: params.revisionKey,
3669
+ sourcePath: params.sourcePath
3670
+ });
3671
+ if (existingUrl) {
3672
+ return existingUrl;
3673
+ }
3674
+ try {
3675
+ const fetched = await fetchAsset(params.sourceUrl, params.fallbackContentType);
3676
+ const written = await params.storageConfig.storage.writeAsset({
3677
+ documentId: params.documentId,
3678
+ revisionKey: params.revisionKey,
3679
+ sourcePath: params.sourcePath,
3680
+ body: fetched.body,
3681
+ contentType: fetched.contentType,
3682
+ metadata: params.metadata
3683
+ });
3684
+ return written.url ?? await params.storageConfig.storage.getAssetUrl({
3685
+ documentId: params.documentId,
3686
+ revisionKey: params.revisionKey,
3687
+ sourcePath: params.sourcePath
3688
+ }) ?? void 0;
3689
+ } catch {
3690
+ return void 0;
3691
+ }
3692
+ }
3693
+ async function fetchAsset(sourceUrl, fallbackContentType) {
3694
+ const response = await fetch(sourceUrl);
3695
+ if (!response.ok) {
3696
+ throw new Error(`Unable to fetch parsed asset: ${response.status}`);
3697
+ }
3698
+ return {
3699
+ body: new Uint8Array(await response.arrayBuffer()),
3700
+ contentType: response.headers.get("content-type") ?? fallbackContentType
3701
+ };
3702
+ }
3703
+ function removeAssetUrlsFromMetadata(metadata) {
3704
+ const cloned = cloneMetadata(metadata);
3705
+ const pageAssets = toUnknownArray(cloned.pageAssets);
3706
+ if (!pageAssets) {
3707
+ return cloned;
3708
+ }
3709
+ cloned.pageAssets = pageAssets.map((pageAsset) => {
3710
+ if (!isRecord2(pageAsset)) {
3711
+ return pageAsset;
3712
+ }
3713
+ return removeAssetUrlField(pageAsset);
3714
+ });
3715
+ return cloned;
3716
+ }
3717
+ function removeAssetUrlField(value) {
3718
+ const cloned = { ...value };
3719
+ delete cloned.assetUrl;
3720
+ return cloned;
3721
+ }
3722
+ function toSnapshotChunkPageFromReadChunks(params) {
3723
+ return {
3724
+ version: 1,
3725
+ jobId: params.response.jobId ?? params.revisionKey,
3726
+ revisionKey: params.revisionKey,
3727
+ documentId: params.response.documentId,
3728
+ namespace: params.response.namespace,
3729
+ sourceFileName: params.response.documentId,
3730
+ page: params.response.pagination.page,
3731
+ pageSize: params.response.pagination.pageSize,
3732
+ total: params.response.pagination.total,
3733
+ totalPages: params.response.pagination.totalPages,
3734
+ chunks: params.chunks.map(toSnapshotChunkFromReadChunk)
3735
+ };
3736
+ }
3737
+ function toSnapshotChunkFromReadChunk(chunk) {
3738
+ return {
3739
+ id: chunk.chunkId,
3740
+ chunkId: chunk.chunkId,
3741
+ chunkType: chunk.chunkType,
3742
+ contentSource: chunk.contentSource,
3743
+ content: chunk.content,
3744
+ sectionPath: chunk.sectionPath,
3745
+ sourceChunkPath: chunk.sourceChunkPath,
3746
+ filePath: chunk.filePath,
3747
+ sortOrder: chunk.position,
3748
+ metadata: cloneMetadata(chunk.metadata),
3749
+ assetUrl: chunk.assetUrl
3750
+ };
3751
+ }
3752
+ function createRemoteSnapshotManifest(params) {
3753
+ return {
3754
+ version: 1,
3755
+ kind: "knowhere-parsed-result-snapshot",
3756
+ jobId: params.response.jobId ?? params.revisionKey,
3757
+ revisionKey: params.revisionKey,
3758
+ documentId: params.response.documentId,
3759
+ namespace: params.response.namespace,
3760
+ sourceFileName: params.response.documentId,
3761
+ totalChunks: params.response.pagination.total,
3762
+ typeCounts: countSnapshotPagesTypes(params.chunkPages),
3763
+ chunkPageSize: params.response.pagination.pageSize,
3764
+ chunkPages: Array.from({ length: params.response.pagination.totalPages }, (_, index) => ({
3765
+ page: index + 1,
3766
+ pageSize: params.response.pagination.pageSize,
3767
+ chunkCount: index + 1 === params.response.pagination.totalPages ? params.response.pagination.total - params.response.pagination.pageSize * (params.response.pagination.totalPages - 1) : params.response.pagination.pageSize,
3768
+ key: `parsed/chunks/page-${index + 1}.json`
3769
+ })),
3770
+ assetUrlsByFilePath: {},
3771
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
3772
+ };
3773
+ }
3774
+ function countSnapshotPagesTypes(pages) {
3775
+ return pages.flatMap((page) => page.chunks).reduce(
3776
+ (counts, chunk) => {
3777
+ const chunkType = chunk.chunkType;
3778
+ counts[chunkType] += 1;
3779
+ return counts;
3780
+ },
3781
+ { text: 0, image: 0, table: 0, page: 0 }
3782
+ );
3783
+ }
3784
+ function countRemoteChunkTypes(chunks) {
3785
+ return chunks.reduce(
3786
+ (counts, chunk) => {
3787
+ counts[chunk.chunkType] += 1;
3788
+ return counts;
3789
+ },
3790
+ { text: 0, image: 0, table: 0, page: 0 }
3791
+ );
3792
+ }
3793
+ function createContinuationCursor(cursor) {
3794
+ return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
3795
+ }
3796
+ function createGrepContinuationCursor(params) {
3797
+ if (params.matchIndex + 1 < params.chunkMatchCount) {
3798
+ return createContinuationCursor({
3799
+ documentId: params.documentId,
3800
+ revisionKey: params.revisionKey,
3801
+ nextPage: params.response.pagination.page,
3802
+ nextChunkIndex: params.chunkIndex,
3803
+ nextMatchIndex: params.matchIndex + 1
3804
+ });
3805
+ }
3806
+ if (params.chunkIndex + 1 < params.response.chunks.length) {
3807
+ return createContinuationCursor({
3808
+ documentId: params.documentId,
3809
+ revisionKey: params.revisionKey,
3810
+ nextPage: params.response.pagination.page,
3811
+ nextChunkIndex: params.chunkIndex + 1,
3812
+ nextMatchIndex: 0
3813
+ });
3814
+ }
3815
+ if (params.response.pagination.page < params.response.pagination.totalPages) {
3816
+ return createContinuationCursor({
3817
+ documentId: params.documentId,
3818
+ revisionKey: params.revisionKey,
3819
+ nextPage: params.response.pagination.page + 1,
3820
+ nextChunkIndex: 0,
3821
+ nextMatchIndex: 0
3822
+ });
3823
+ }
3824
+ return void 0;
3825
+ }
3826
+ function parseContinuationCursor(value) {
3827
+ if (!value) {
3828
+ return null;
3829
+ }
3830
+ try {
3831
+ const parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
3832
+ if (!isRecord2(parsed)) {
3833
+ return null;
3834
+ }
3835
+ const documentId = parsed.documentId;
3836
+ const revisionKey = parsed.revisionKey;
3837
+ const nextPage = parsed.nextPage;
3838
+ const nextChunkIndex = parsed.nextChunkIndex;
3839
+ const nextMatchIndex = parsed.nextMatchIndex;
3840
+ if (typeof documentId !== "string" || typeof revisionKey !== "string" || typeof nextPage !== "number" || !Number.isInteger(nextPage) || nextPage < 1 || !isOptionalNonNegativeInteger(nextChunkIndex) || !isOptionalNonNegativeInteger(nextMatchIndex)) {
3841
+ return null;
3842
+ }
3843
+ return {
3844
+ documentId,
3845
+ revisionKey,
3846
+ nextPage,
3847
+ nextChunkIndex,
3848
+ nextMatchIndex
3849
+ };
3850
+ } catch {
3851
+ return null;
3852
+ }
3853
+ }
3854
+ function isOptionalNonNegativeInteger(value) {
3855
+ return value === void 0 || typeof value === "number" && Number.isInteger(value) && value >= 0;
3856
+ }
3857
+ function cloneMetadata(metadata) {
3858
+ return { ...metadata };
3859
+ }
3860
+ function stringifyFlatMetadata(metadata) {
3861
+ const entries = Object.entries(metadata).flatMap(([key, value]) => {
3862
+ if (typeof value === "string") {
3863
+ return [[key, value]];
3864
+ }
3865
+ if (typeof value === "number" || typeof value === "boolean") {
3866
+ return [[key, String(value)]];
3867
+ }
3868
+ return [];
3869
+ });
3870
+ return Object.fromEntries(entries);
3871
+ }
3872
+ function toUnknownArray(value) {
3873
+ return Array.isArray(value) ? value : null;
3874
+ }
3875
+ function inferContentTypeFromPath(filePath) {
3876
+ const extension = filePath.split(".").at(-1)?.toLowerCase();
3877
+ if (extension === "png") return "image/png";
3878
+ if (extension === "jpg" || extension === "jpeg") return "image/jpeg";
3879
+ if (extension === "html" || extension === "htm") return "text/html; charset=utf-8";
3880
+ return "application/octet-stream";
3881
+ }
3882
+ function isRecord2(value) {
3883
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3884
+ }
2061
3885
  function indexChunks(result) {
2062
3886
  return result.chunks.map((chunk, index) => {
2063
3887
  const filePath = getChunkFilePath2(chunk);
3888
+ const readableContent = getReadableChunkContent(chunk);
2064
3889
  return {
2065
3890
  source: chunk,
2066
3891
  position: index + 1,
2067
3892
  chunkId: chunk.chunkId,
2068
3893
  chunkType: chunk.type,
3894
+ contentSource: chunk.contentSource,
2069
3895
  content: chunk.content,
3896
+ readableContent,
2070
3897
  sectionPath: normalizeSectionPath(chunk.path, result.manifest.sourceFileName),
2071
3898
  sourceChunkPath: chunk.path,
2072
3899
  filePath,
3900
+ assetUrl: getChunkAssetUrl(chunk),
3901
+ pageNumbers: getChunkPageNumbers(chunk.metadata),
2073
3902
  metadata: chunk.metadata
2074
3903
  };
2075
3904
  });
2076
3905
  }
3906
+ function getReadableChunkContent(chunk) {
3907
+ if (chunk.type !== "page") {
3908
+ return chunk.content;
3909
+ }
3910
+ const summary = chunk.metadata.summary;
3911
+ return typeof summary === "string" && summary.trim().length > 0 ? summary : chunk.content;
3912
+ }
3913
+ function getChunkPageNumbers(metadata) {
3914
+ const values = [metadata.pageNums, metadata.page_nums].filter(Array.isArray);
3915
+ const pageNumbers = values.flatMap(
3916
+ (value) => value.filter(
3917
+ (pageNumber) => Number.isInteger(pageNumber) && pageNumber > 0
3918
+ )
3919
+ );
3920
+ return pageNumbers.length > 0 ? [...new Set(pageNumbers)].sort((left, right) => left - right) : void 0;
3921
+ }
2077
3922
  function getChunkFilePath2(chunk) {
2078
3923
  if (chunk.type === "image" || chunk.type === "table") {
2079
3924
  return chunk.filePath;
@@ -2081,14 +3926,17 @@ function getChunkFilePath2(chunk) {
2081
3926
  const filePath = chunk.metadata.filePath;
2082
3927
  return typeof filePath === "string" ? filePath : void 0;
2083
3928
  }
2084
- function normalizeSectionPath(path3, sourceFileName) {
2085
- if (!path3) {
3929
+ function getChunkAssetUrl(chunk) {
3930
+ return chunk.type === "image" || chunk.type === "table" ? chunk.assetUrl : void 0;
3931
+ }
3932
+ function normalizeSectionPath(path4, sourceFileName) {
3933
+ if (!path4) {
2086
3934
  return "";
2087
3935
  }
2088
- if (path3.startsWith("images/") || path3.startsWith("tables/")) {
2089
- return path3;
3936
+ if (path4.startsWith("images/") || path4.startsWith("tables/")) {
3937
+ return path4;
2090
3938
  }
2091
- const parts = path3.split("/").filter(Boolean);
3939
+ const parts = path4.split("/").filter(Boolean);
2092
3940
  if (sourceFileName) {
2093
3941
  const fileNameIndex = parts.indexOf(sourceFileName);
2094
3942
  if (fileNameIndex >= 0) {
@@ -2110,12 +3958,12 @@ function buildFlatSections(result, chunks) {
2110
3958
  }
2111
3959
  const byPath = /* @__PURE__ */ new Map();
2112
3960
  for (const chunk of chunks) {
2113
- const path3 = chunk.sectionPath || chunk.sourceChunkPath;
2114
- const existing = byPath.get(path3);
3961
+ const path4 = chunk.sectionPath || chunk.sourceChunkPath;
3962
+ const existing = byPath.get(path4);
2115
3963
  if (existing) {
2116
3964
  addChunkToSection(existing, chunk);
2117
3965
  } else {
2118
- byPath.set(path3, createSectionFromChunk(path3, chunk));
3966
+ byPath.set(path4, createSectionFromChunk(path4, chunk));
2119
3967
  }
2120
3968
  }
2121
3969
  return [...byPath.values()].sort(compareSections);
@@ -2147,7 +3995,7 @@ function createSectionFromChunk(pathValue, chunk) {
2147
3995
  startChunk: chunk.position,
2148
3996
  endChunk: chunk.position,
2149
3997
  chunkCount: 1,
2150
- typeCounts: { text: 0, image: 0, table: 0, [chunk.chunkType]: 1 },
3998
+ typeCounts: { text: 0, image: 0, table: 0, page: 0, [chunk.chunkType]: 1 },
2151
3999
  children: []
2152
4000
  };
2153
4001
  }
@@ -2206,7 +4054,7 @@ function countIndexedTypes(chunks) {
2206
4054
  counts[chunk.chunkType] += 1;
2207
4055
  return counts;
2208
4056
  },
2209
- { text: 0, image: 0, table: 0 }
4057
+ { text: 0, image: 0, table: 0, page: 0 }
2210
4058
  );
2211
4059
  }
2212
4060
  function isInSection(chunkSectionPath, sectionPath) {
@@ -2243,10 +4091,14 @@ function toReadChunk(chunk) {
2243
4091
  position: chunk.position,
2244
4092
  chunkId: chunk.chunkId,
2245
4093
  chunkType: chunk.chunkType,
4094
+ contentSource: chunk.contentSource,
2246
4095
  content: chunk.content,
4096
+ readableContent: chunk.readableContent,
2247
4097
  sectionPath: chunk.sectionPath,
2248
4098
  sourceChunkPath: chunk.sourceChunkPath,
2249
4099
  filePath: chunk.filePath,
4100
+ assetUrl: chunk.assetUrl,
4101
+ pageNumbers: chunk.pageNumbers,
2250
4102
  metadata: chunk.metadata
2251
4103
  };
2252
4104
  }
@@ -2298,6 +4150,7 @@ function toResultReference(result, documentByServerId) {
2298
4150
  return {
2299
4151
  localDocumentId: documentId ? documentByServerId.get(documentId)?.localDocumentId : void 0,
2300
4152
  documentId,
4153
+ chunkId: result.chunkId,
2301
4154
  sectionPath: result.source.sectionPath ?? void 0,
2302
4155
  chunkType: result.chunkType,
2303
4156
  score: result.score
@@ -2308,6 +4161,7 @@ function toRemoteSearchResult(result, documentByServerId) {
2308
4161
  return {
2309
4162
  localDocumentId: documentId ? documentByServerId.get(documentId)?.localDocumentId : void 0,
2310
4163
  documentId,
4164
+ chunkId: result.chunkId,
2311
4165
  chunkType: result.chunkType,
2312
4166
  content: result.content,
2313
4167
  score: result.score,
@@ -2420,7 +4274,12 @@ var Knowhere = class {
2420
4274
  const result = await this.jobs.load(jobResult, {
2421
4275
  verifyChecksum: params.verifyChecksum
2422
4276
  });
2423
- return enrichParseResult(result, jobResult);
4277
+ const enrichedResult = enrichParseResult(result, {
4278
+ namespace: jobResult.namespace,
4279
+ documentId: jobResult.documentId ?? params.documentId
4280
+ });
4281
+ const storedResult = await storeParseResultAssets(enrichedResult, params.storageAdapter);
4282
+ return storedResult.result;
2424
4283
  }
2425
4284
  /**
2426
4285
  * Start a parse job and return immediately after the URL job is created or
@@ -2463,6 +4322,145 @@ var Knowhere = class {
2463
4322
  return job;
2464
4323
  }
2465
4324
  };
4325
+
4326
+ // src/storage/disk-parsed-document-storage.ts
4327
+ var import_crypto3 = require("crypto");
4328
+ var import_fs4 = require("fs");
4329
+ var import_path4 = __toESM(require("path"));
4330
+ var DiskParsedDocumentStorage = class {
4331
+ rootDirectory;
4332
+ constructor(rootDirectory) {
4333
+ this.rootDirectory = rootDirectory;
4334
+ }
4335
+ async readManifest(params) {
4336
+ return this.readOptionalJson(
4337
+ this.getManifestPath(params.documentId, params.revisionKey)
4338
+ );
4339
+ }
4340
+ async writeManifest(params) {
4341
+ await this.writeJson(
4342
+ this.getManifestPath(params.documentId, params.revisionKey),
4343
+ params.manifest
4344
+ );
4345
+ }
4346
+ async readChunkPage(params) {
4347
+ return this.readOptionalJson(
4348
+ this.getChunkPagePath(params.documentId, params.revisionKey, params.page)
4349
+ );
4350
+ }
4351
+ async writeChunkPage(params) {
4352
+ await this.writeJson(
4353
+ this.getChunkPagePath(params.documentId, params.revisionKey, params.page.page),
4354
+ params.page
4355
+ );
4356
+ }
4357
+ async writeAsset(params) {
4358
+ const assetPath = this.getAssetPath(params.documentId, params.revisionKey, params.sourcePath);
4359
+ await import_fs4.promises.mkdir(import_path4.default.dirname(assetPath), { recursive: true });
4360
+ await import_fs4.promises.writeFile(assetPath, params.body);
4361
+ await this.writeJson(`${assetPath}.metadata.json`, {
4362
+ contentType: params.contentType,
4363
+ metadata: params.metadata ?? {}
4364
+ });
4365
+ return {
4366
+ sourcePath: params.sourcePath,
4367
+ url: toFileUrl(assetPath)
4368
+ };
4369
+ }
4370
+ async getAssetUrl(params) {
4371
+ const assetPath = this.getAssetPath(params.documentId, params.revisionKey, params.sourcePath);
4372
+ try {
4373
+ await import_fs4.promises.access(assetPath);
4374
+ return toFileUrl(assetPath);
4375
+ } catch (error) {
4376
+ if (isMissingFileError3(error)) {
4377
+ return null;
4378
+ }
4379
+ throw error;
4380
+ }
4381
+ }
4382
+ async readSyncProgress(params) {
4383
+ return this.readOptionalJson(
4384
+ this.getProgressPath(params.documentId, params.revisionKey)
4385
+ );
4386
+ }
4387
+ async writeSyncProgress(params) {
4388
+ await this.writeJson(this.getProgressPath(params.documentId, params.revisionKey), params);
4389
+ }
4390
+ getManifestPath(documentId, revisionKey) {
4391
+ return import_path4.default.join(
4392
+ this.getRevisionDirectory(documentId, revisionKey),
4393
+ "manifest",
4394
+ "current.json"
4395
+ );
4396
+ }
4397
+ getChunkPagePath(documentId, revisionKey, page) {
4398
+ return import_path4.default.join(
4399
+ this.getRevisionDirectory(documentId, revisionKey),
4400
+ "chunks",
4401
+ `page-${page}.json`
4402
+ );
4403
+ }
4404
+ getProgressPath(documentId, revisionKey) {
4405
+ return import_path4.default.join(this.getRevisionDirectory(documentId, revisionKey), "sync-progress.json");
4406
+ }
4407
+ getAssetPath(documentId, revisionKey, sourcePath) {
4408
+ const normalizedSourcePath = normalizeRelativeStoragePath(sourcePath);
4409
+ const assetPath = import_path4.default.resolve(
4410
+ this.getRevisionDirectory(documentId, revisionKey),
4411
+ "assets",
4412
+ normalizedSourcePath
4413
+ );
4414
+ const assetsDirectory = import_path4.default.resolve(
4415
+ this.getRevisionDirectory(documentId, revisionKey),
4416
+ "assets"
4417
+ );
4418
+ if (!isPathInsideDirectory2(assetPath, assetsDirectory)) {
4419
+ throw new Error(`Parsed asset path resolves outside storage: ${sourcePath}`);
4420
+ }
4421
+ return assetPath;
4422
+ }
4423
+ getRevisionDirectory(documentId, revisionKey) {
4424
+ return import_path4.default.join(this.rootDirectory, hashPathPart(documentId), hashPathPart(revisionKey));
4425
+ }
4426
+ async readOptionalJson(filePath) {
4427
+ try {
4428
+ return JSON.parse(await import_fs4.promises.readFile(filePath, "utf8"));
4429
+ } catch (error) {
4430
+ if (isMissingFileError3(error)) {
4431
+ return null;
4432
+ }
4433
+ throw error;
4434
+ }
4435
+ }
4436
+ async writeJson(filePath, value) {
4437
+ await import_fs4.promises.mkdir(import_path4.default.dirname(filePath), { recursive: true });
4438
+ await import_fs4.promises.writeFile(filePath, JSON.stringify(value, null, 2), {
4439
+ encoding: "utf8",
4440
+ flag: "w"
4441
+ });
4442
+ }
4443
+ };
4444
+ function hashPathPart(value) {
4445
+ return (0, import_crypto3.createHash)("sha256").update(value).digest("hex").slice(0, 24);
4446
+ }
4447
+ function normalizeRelativeStoragePath(value) {
4448
+ const normalized = value.replaceAll("\\", "/").replace(/^\.\/+/, "");
4449
+ if (normalized.length === 0 || normalized.includes("\0") || normalized.startsWith("/") || normalized.split("/").some((part) => part.length === 0 || part === "." || part === "..")) {
4450
+ throw new Error(`Invalid parsed storage path: ${value}`);
4451
+ }
4452
+ return normalized;
4453
+ }
4454
+ function isPathInsideDirectory2(filePath, directory) {
4455
+ const relative = import_path4.default.relative(directory, filePath);
4456
+ return relative === "" || !relative.startsWith("..") && !import_path4.default.isAbsolute(relative);
4457
+ }
4458
+ function toFileUrl(filePath) {
4459
+ return `file://${filePath}`;
4460
+ }
4461
+ function isMissingFileError3(error) {
4462
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
4463
+ }
2466
4464
  // Annotate the CommonJS export names for ESM import in node:
2467
4465
  0 && (module.exports = {
2468
4466
  APIError,
@@ -2470,6 +4468,7 @@ var Knowhere = class {
2470
4468
  BadRequestError,
2471
4469
  ChecksumError,
2472
4470
  ConflictError,
4471
+ DiskParsedDocumentStorage,
2473
4472
  Documents,
2474
4473
  GatewayTimeoutError,
2475
4474
  InternalServerError,
@@ -2490,5 +4489,7 @@ var Knowhere = class {
2490
4489
  ServiceUnavailableError,
2491
4490
  TimeoutError,
2492
4491
  VERSION,
2493
- ValidationError
4492
+ ValidationError,
4493
+ storeParseResultAssets,
4494
+ syncParseResultToParsedDocumentStorage
2494
4495
  });