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