@webskill/sdk 0.27.1 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/agent.d.ts +3 -3
  2. package/dist/agent.js +901 -120
  3. package/dist/{approval-BSwffz8P.js → approval-DnUytrkq.js} +7 -2
  4. package/dist/browser.d.ts +12 -12
  5. package/dist/browser.js +527 -69
  6. package/dist/{geometry-BXMPNuM6.js → geometry-QmrkJw1_.js} +13 -2
  7. package/dist/governance.d.ts +3 -3
  8. package/dist/{index--GvzV6Lq.d.ts → index-B5zHZDNA.d.ts} +91 -6
  9. package/dist/{index-C8-9-8zo.d.ts → index-C3t1jKAs.d.ts} +41 -13
  10. package/dist/{index-D6eSrEi5.d.ts → index-Cngzg2WS.d.ts} +2 -2
  11. package/dist/index.d.ts +4 -4
  12. package/dist/index.js +12 -7
  13. package/dist/{linkedDocument-0iiVPd5t.js → linkedDocument-GE-VV_Gj.js} +112 -2
  14. package/dist/mcp.d.ts +2 -2
  15. package/dist/{miniChart-D0nYMzz8.js → miniChart-k95Dpy0q.js} +82 -4
  16. package/dist/node.d.ts +4 -3
  17. package/dist/node.js +19 -2
  18. package/dist/{openUiLibrary-CxtrnX54.js → openUiLibrary-BnhYUlqY.js} +1 -1
  19. package/dist/{openUiSpecLang-DIcLcybq.js → openUiSpecLang-C8qNOxzA.js} +432 -19
  20. package/dist/{renderResult-D9Q-Vu2x.js → renderResult-2v3WQzs_.js} +5 -2
  21. package/dist/{skillVersionStore-S4_HJ_Fl-Ds97pwm5.d.ts → skillVersionStore-S4_HJ_Fl-AiNsSSdO.d.ts} +1 -1
  22. package/dist/testing.d.ts +1 -1
  23. package/dist/{types-CpDRZ0rA-CFQ-vdKz.d.ts → types-wKp3YBSM-D7w-ZbsB.d.ts} +8 -3
  24. package/dist/ui-react.d.ts +3 -3
  25. package/dist/ui-react.js +5767 -793
  26. package/dist/ui-vue.d.ts +1 -1
  27. package/dist/ui-vue.js +1 -1
  28. package/dist/ui.d.ts +4 -4
  29. package/dist/ui.js +13 -7
  30. package/dist/{webSkillApi-C_7CTwtB.js → webSkillApi-TeWPPFDn.js} +1 -1
  31. package/dist/{webskillLitCatalog-BPyjYuRT.js → webskillLitCatalog-MfiLE5aq.js} +41 -2
  32. package/package.json +3 -1
  33. package/dist/kind-Dc8x0HWz.js +0 -110
package/dist/browser.js CHANGED
@@ -2,14 +2,14 @@ import { n as messageOf$1, t as WebSkillError } from "./errors-BDZNpC13.js";
2
2
  import { A as SKILL_MANIFEST_FILE, D as verifySkillSignature, M as buildManifest, O as MANIFEST_EXCLUDED_FILES, P as verifyManifest, b as parseSkillMarkdown, c as unzipWithLimits, f as SKILL_PACK_FILE, i as buildCatalog, k as SKILLS_LOCKFILE, m as parseSkillPackManifest, n as SkillDiscovery, o as readResponseWithLimit, p as exportSkills, t as validateSkills, u as detectSkillArchiveShapeFromFs, w as readSkillSignature, y as isValidSkillName } from "./skill-WSUDp6A1.js";
3
3
  import { o as atomicWriteText, r as resolveInsideRoot, s as isAtomicTempPath } from "./pathSecurity-CirkQwWP.js";
4
4
  import { t as assertRemoteUrlAllowed } from "./urlSafety-CiSuCJvX.js";
5
- import { t as ATTACHMENT_TEXT_LIMIT } from "./kind-Dc8x0HWz.js";
6
- import { _ as FsRunSnapshotStore, a as networkPolicyLibSource, c as bridgeError, ct as ProgressiveRouter, d as FsMemoryStore, l as parseBridgeRequest, m as AgentLoop, r as normalizeToolError, s as UPLOAD_FILES_UNAVAILABLE, t as CapabilityApproval, u as FsArtifactStore } from "./approval-BSwffz8P.js";
5
+ import { c as toBase64$1, f as SPREADSHEET_MIME_TYPE, y as ATTACHMENT_TEXT_LIMIT } from "./linkedDocument-GE-VV_Gj.js";
6
+ import { _ as FsRunSnapshotStore, a as networkPolicyLibSource, c as bridgeError, ct as ProgressiveRouter, d as FsMemoryStore, l as parseBridgeRequest, m as AgentLoop, r as normalizeToolError, s as UPLOAD_FILES_UNAVAILABLE, t as CapabilityApproval, u as FsArtifactStore } from "./approval-DnUytrkq.js";
7
7
  import { a as GoogleGenAiClient, o as AnthropicClient, s as OpenAiCompatibleClient, t as MockLlmClient } from "./llm-eIQNO9tr.js";
8
- import { c as toBase64$1, f as SPREADSHEET_MIME_TYPE } from "./linkedDocument-0iiVPd5t.js";
9
8
  import { n as normalizeToolContent, t as mergeCatalogEntries } from "./external-_ZRQe-V9.js";
10
- import { t as createWebSkillApi } from "./webSkillApi-C_7CTwtB.js";
11
- import { A as frameSteps, D as SCROLL_DIRECTIONS, E as toActionFrameScopes, O as toFrameScopes, _ as defersImage, a as columnWidthToPixels, b as isPdfTextTrustworthy, c as isRangeTopLeft, d as rangeContains, f as readImageSize, k as frameLabel, l as parseCellAddress, n as EMU_PER_POINT, o as fitWithin, p as PDF_LARGE_IMAGE_RATIO, r as EMU_PER_TWIP$1, s as formatCellAddress, t as EMU_PER_PIXEL, u as parseCellRange, v as takesImage } from "./geometry-BXMPNuM6.js";
12
- import { zipSync } from "fflate";
9
+ import { t as createWebSkillApi } from "./webSkillApi-TeWPPFDn.js";
10
+ import { A as frameSteps, D as SCROLL_DIRECTIONS, E as toActionFrameScopes, O as toFrameScopes, _ as defersImage, a as columnWidthToPixels, b as isPdfTextTrustworthy, c as isRangeTopLeft, d as rangeContains, f as readImageSize, k as frameLabel, l as parseCellAddress, n as EMU_PER_POINT, o as fitWithin, p as PDF_LARGE_IMAGE_RATIO, r as EMU_PER_TWIP$1, s as formatCellAddress, t as EMU_PER_PIXEL, u as parseCellRange, v as takesImage } from "./geometry-QmrkJw1_.js";
11
+ import { Unzip, UnzipInflate, zipSync } from "fflate";
12
+ import { SaxesParser } from "saxes";
13
13
 
14
14
  //#region ../browser/src/fs/featureDetection.ts
15
15
  /** 检测当前环境是否可用 OPFS(navigator.storage.getDirectory) */
@@ -98,6 +98,21 @@ var OpfsProvider = class {
98
98
  return new Uint8Array(await (await handle.getFile()).arrayBuffer());
99
99
  });
100
100
  }
101
+ async createReadStream(p) {
102
+ const stream = (await this.#wrap(p, async () => (await this.#walkFile(p)).getFile())).stream();
103
+ return (async function* () {
104
+ const reader = stream.getReader();
105
+ try {
106
+ for (;;) {
107
+ const { done, value } = await reader.read();
108
+ if (done) return;
109
+ if (value !== void 0) yield value;
110
+ }
111
+ } finally {
112
+ reader.releaseLock();
113
+ }
114
+ })();
115
+ }
101
116
  async writeBinary(p, content) {
102
117
  await this.#wrap(p, async () => {
103
118
  const segments = this.#segments(p);
@@ -6408,14 +6423,129 @@ async function extractDocxText(bytes) {
6408
6423
  return blocks.join("\n").replace(/\n{3,}/g, "\n\n").trim();
6409
6424
  }
6410
6425
 
6426
+ //#endregion
6427
+ //#region ../browser/src/document/xlsConversion.ts
6428
+ const OLE2_SIGNATURE = [
6429
+ 208,
6430
+ 207,
6431
+ 17,
6432
+ 224,
6433
+ 161,
6434
+ 177,
6435
+ 26,
6436
+ 225
6437
+ ];
6438
+ function isLegacyXls(bytes) {
6439
+ return OLE2_SIGNATURE.every((byte, index) => bytes[index] === byte);
6440
+ }
6441
+ /** 旧 BIFF/OLE2 工作簿只在内存中转换;原件与上传落盘内容都不改写。 */
6442
+ async function convertXlsToXlsx(bytes) {
6443
+ if (!isLegacyXls(bytes)) throw new WebSkillError("TOOL_EXECUTION_FAILED", "The file is not a legacy OLE2 .xls workbook.");
6444
+ try {
6445
+ const [XLSX, codepage] = await Promise.all([import("@e965/xlsx"), import("@e965/xlsx/dist/cpexcel.full.mjs")]);
6446
+ const { cptable } = codepage;
6447
+ const { utils } = codepage;
6448
+ XLSX.set_cptable({
6449
+ ...cptable,
6450
+ utils
6451
+ });
6452
+ const workbook = XLSX.read(bytes, {
6453
+ type: "array",
6454
+ cellDates: false,
6455
+ cellNF: true
6456
+ });
6457
+ const output = XLSX.write(workbook, {
6458
+ type: "array",
6459
+ bookType: "xlsx",
6460
+ compression: true
6461
+ });
6462
+ if (output instanceof Uint8Array) return output;
6463
+ if (output instanceof ArrayBuffer) return new Uint8Array(output);
6464
+ throw new Error("The converter returned an unsupported binary output.");
6465
+ } catch (error) {
6466
+ throw new WebSkillError("TOOL_EXECUTION_FAILED", `The legacy .xls workbook could not be converted to .xlsx: ${error instanceof Error ? error.message : String(error)}`, error);
6467
+ }
6468
+ }
6469
+ async function normalizeExcelBytes(bytes) {
6470
+ return isLegacyXls(bytes) ? convertXlsToXlsx(bytes) : bytes;
6471
+ }
6472
+ async function collect(chunks) {
6473
+ const parts = [];
6474
+ let length = 0;
6475
+ for await (const chunk of chunks) {
6476
+ parts.push(chunk);
6477
+ length += chunk.byteLength;
6478
+ }
6479
+ const bytes = new Uint8Array(length);
6480
+ let offset = 0;
6481
+ for (const part of parts) {
6482
+ bytes.set(part, offset);
6483
+ offset += part.byteLength;
6484
+ }
6485
+ return bytes;
6486
+ }
6487
+ /**
6488
+ * 流式读取先复用首段判断魔数。普通 xlsx 的首段会原样接回流,不整份缓存;
6489
+ * 只有必须整体转换的 xls 才收集全部字节。
6490
+ */
6491
+ async function normalizeExcelSource(source) {
6492
+ if (typeof source !== "function") return normalizeExcelBytes(source);
6493
+ const iterator = (await source())[Symbol.asyncIterator]();
6494
+ const prefix = [];
6495
+ let prefixLength = 0;
6496
+ let done = false;
6497
+ while (prefixLength < OLE2_SIGNATURE.length && !done) {
6498
+ const next = await iterator.next();
6499
+ if (next.done === true) done = true;
6500
+ else {
6501
+ prefix.push(next.value);
6502
+ prefixLength += next.value.byteLength;
6503
+ }
6504
+ }
6505
+ const replay = async function* () {
6506
+ yield* prefix;
6507
+ try {
6508
+ while (!done) {
6509
+ const next = await iterator.next();
6510
+ if (next.done === true) {
6511
+ done = true;
6512
+ break;
6513
+ }
6514
+ yield next.value;
6515
+ }
6516
+ } finally {
6517
+ if (!done) await iterator.return?.();
6518
+ }
6519
+ };
6520
+ const head = new Uint8Array(Math.min(prefixLength, OLE2_SIGNATURE.length));
6521
+ let offset = 0;
6522
+ for (const chunk of prefix) {
6523
+ head.set(chunk.subarray(0, head.byteLength - offset), offset);
6524
+ offset = Math.min(head.byteLength, offset + chunk.byteLength);
6525
+ if (offset === head.byteLength) break;
6526
+ }
6527
+ if (!isLegacyXls(head)) {
6528
+ let pending = replay();
6529
+ return async () => {
6530
+ if (pending !== void 0) {
6531
+ const first = pending;
6532
+ pending = void 0;
6533
+ return first;
6534
+ }
6535
+ return await source();
6536
+ };
6537
+ }
6538
+ return convertXlsToXlsx(await collect(replay()));
6539
+ }
6540
+
6411
6541
  //#endregion
6412
6542
  //#region ../browser/src/document/xlsx.ts
6413
6543
  /**
6414
6544
  * xlsx 文本抽取(0.13.0 分册 12)。
6415
6545
  *
6416
- * **零新增依赖**:xlsx 和 docx 是同一种容器(OPC = zip + XML),
6417
- * 所以沿用 docx 那条路 —— zip 走仓内既有的 `unzipWithLimits`(fflate,
6418
- * 自带解压体积上限,顺带挡住 zip 炸弹),XML 走浏览器原生 `DOMParser`。
6546
+ * xlsx 和 docx 是同一种容器(OPC = zip + XML),所以沿用 docx 那条路 ——
6547
+ * zip 走仓内既有的 `unzipWithLimits`(fflate,自带解压体积上限,顺带挡住 zip 炸弹),
6548
+ * XML 走浏览器原生 `DOMParser`。旧版 xls 先由 `xlsConversion` 在内存中转成 xlsx。
6419
6549
  *
6420
6550
  * ⚠️ `DOMParser` 是浏览器 API,Node 没有 —— 所以这个模块只在 `@webskill/browser`。
6421
6551
  * Node 宿主没有 xlsx 能力,与 docx 同一条**显式声明**的限制,不是静默降级(备案 D55)。
@@ -6437,21 +6567,10 @@ const XLSX_UNEXTRACTED = [
6437
6567
  "hidden row and column marking",
6438
6568
  "formula expressions (only cached values are read)"
6439
6569
  ];
6440
- const WORKBOOK = "xl/workbook.xml";
6441
- const WORKBOOK_RELS = "xl/_rels/workbook.xml.rels";
6442
- const SHARED_STRINGS = "xl/sharedStrings.xml";
6443
- const STYLES = "xl/styles.xml";
6444
- /** OLE2 复合文档签名:旧版 .xls 的开头,它不是 zip,走不到解压那一步 */
6445
- const OLE2_SIGNATURE = [
6446
- 208,
6447
- 207,
6448
- 17,
6449
- 224,
6450
- 161,
6451
- 177,
6452
- 26,
6453
- 225
6454
- ];
6570
+ const WORKBOOK$1 = "xl/workbook.xml";
6571
+ const WORKBOOK_RELS$1 = "xl/_rels/workbook.xml.rels";
6572
+ const SHARED_STRINGS$1 = "xl/sharedStrings.xml";
6573
+ const STYLES$1 = "xl/styles.xml";
6455
6574
  /** 内建的日期/时间数字格式 id(ECMA-376 §18.8.30) */
6456
6575
  const BUILTIN_DATE_FORMATS = /* @__PURE__ */ new Set([
6457
6576
  14,
@@ -6467,14 +6586,14 @@ const BUILTIN_DATE_FORMATS = /* @__PURE__ */ new Set([
6467
6586
  46,
6468
6587
  47
6469
6588
  ]);
6470
- function fail(message) {
6589
+ function fail$1(message) {
6471
6590
  throw new WebSkillError("TOOL_EXECUTION_FAILED", message);
6472
6591
  }
6473
6592
  /** 按 localName 取子孙元素,忽略命名空间前缀:不同生成器的前缀写法并不统一 */
6474
- const byTag$3 = (scope, name) => [...scope.getElementsByTagNameNS("*", name)];
6475
- function parseXml$3(bytes, what) {
6593
+ const byTag$4 = (scope, name) => [...scope.getElementsByTagNameNS("*", name)];
6594
+ function parseXml$4(bytes, what) {
6476
6595
  const doc = new DOMParser().parseFromString(new TextDecoder().decode(bytes), "application/xml");
6477
- if (doc.getElementsByTagName("parsererror").length > 0) fail(`The workbook ${what} could not be parsed as XML.`);
6596
+ if (doc.getElementsByTagName("parsererror").length > 0) fail$1(`The workbook ${what} could not be parsed as XML.`);
6478
6597
  return doc;
6479
6598
  }
6480
6599
  /** `B` → 1、`AA` → 26;拿不到列号时返回 undefined,由调用方顺位补上 */
@@ -6519,14 +6638,14 @@ function isDateFormatCode(code) {
6519
6638
  /** 单元格样式索引 → 是否日期格式 */
6520
6639
  function readDateStyles(bytes) {
6521
6640
  if (bytes === void 0) return [];
6522
- const doc = parseXml$3(bytes, "styles");
6641
+ const doc = parseXml$4(bytes, "styles");
6523
6642
  const custom = /* @__PURE__ */ new Map();
6524
- for (const fmt of byTag$3(doc, "numFmt")) {
6643
+ for (const fmt of byTag$4(doc, "numFmt")) {
6525
6644
  const id = Number(fmt.getAttribute("numFmtId"));
6526
6645
  const code = fmt.getAttribute("formatCode");
6527
6646
  if (Number.isFinite(id) && code !== null) custom.set(id, code);
6528
6647
  }
6529
- const cellXfs = byTag$3(doc, "cellXfs")[0];
6648
+ const cellXfs = byTag$4(doc, "cellXfs")[0];
6530
6649
  if (cellXfs === void 0) return [];
6531
6650
  return [...cellXfs.children].filter((xf) => xf.localName === "xf").map((xf) => {
6532
6651
  const id = Number(xf.getAttribute("numFmtId") ?? "0");
@@ -6591,7 +6710,7 @@ function cellText$1(cell, shared, dateStyles, date1904) {
6591
6710
  function sheetRows(doc, shared, dateStyles, date1904) {
6592
6711
  const rows = [];
6593
6712
  let previousRow = 0;
6594
- for (const row of byTag$3(doc, "row")) {
6713
+ for (const row of byTag$4(doc, "row")) {
6595
6714
  const declared = Number(row.getAttribute("r"));
6596
6715
  const rowNumber = Number.isInteger(declared) && declared > 0 ? declared : previousRow + 1;
6597
6716
  for (let gap = previousRow + 1; gap < rowNumber; gap++) rows.push([]);
@@ -6614,49 +6733,48 @@ function sheetRows(doc, shared, dateStyles, date1904) {
6614
6733
  }
6615
6734
  /** 一行的文本:单元格用制表符分隔(与 docx 的表格同口径),行尾制表符不留 */
6616
6735
  const rowLine = (cells) => cells.join(" ").replace(/\t+$/, "");
6617
- const looksLikeOle2 = (bytes) => bytes.length >= OLE2_SIGNATURE.length && OLE2_SIGNATURE.every((byte, i) => bytes[i] === byte);
6618
6736
  /** rels 里的 Target 可能是相对 `xl/` 的,也可能是包根绝对路径 */
6619
- function resolveSheetPath(target) {
6737
+ function resolveSheetPath$1(target) {
6620
6738
  const clean = target.replace(/^\.\//, "");
6621
6739
  return clean.startsWith("/") ? clean.slice(1) : `xl/${clean}`;
6622
6740
  }
6623
6741
  /**
6624
6742
  * 把 xlsx 拆成「每张表的每一行文本」。
6625
6743
  *
6626
- * @throws WebSkillError 归档损坏、旧版 `.xls`、缺 `xl/workbook.xml`、工作表关系解不开、XML 解析失败
6744
+ * @throws WebSkillError 归档损坏、旧版 `.xls` 转换失败、缺 `xl/workbook.xml`、工作表关系解不开、XML 解析失败
6627
6745
  */
6628
6746
  async function readXlsxWorkbook(bytes) {
6629
- if (looksLikeOle2(bytes)) fail("This is a legacy .xls workbook, which is not supported. Save it as .xlsx and try again.");
6747
+ const normalized = await normalizeExcelBytes(bytes);
6630
6748
  let entries;
6631
6749
  try {
6632
- entries = await unzipWithLimits(bytes);
6750
+ entries = await unzipWithLimits(normalized);
6633
6751
  } catch {
6634
- fail("This file could not be read as an Excel workbook: the archive is damaged or not a .xlsx file.");
6752
+ fail$1("This file could not be read as an Excel workbook: the archive is damaged or not a .xlsx file.");
6635
6753
  }
6636
6754
  const parts = new Map(entries);
6637
- const workbookBytes = parts.get(WORKBOOK);
6638
- if (workbookBytes === void 0) fail(`This file is not an Excel workbook: it has no ${WORKBOOK} entry.`);
6639
- const workbook = parseXml$3(workbookBytes, "index");
6640
- const workbookPr = byTag$3(workbook, "workbookPr")[0];
6755
+ const workbookBytes = parts.get(WORKBOOK$1);
6756
+ if (workbookBytes === void 0) fail$1(`This file is not an Excel workbook: it has no ${WORKBOOK$1} entry.`);
6757
+ const workbook = parseXml$4(workbookBytes, "index");
6758
+ const workbookPr = byTag$4(workbook, "workbookPr")[0];
6641
6759
  const date1904 = workbookPr?.getAttribute("date1904") === "1" || workbookPr?.getAttribute("date1904") === "true";
6642
- const relsBytes = parts.get(WORKBOOK_RELS);
6760
+ const relsBytes = parts.get(WORKBOOK_RELS$1);
6643
6761
  const targets = /* @__PURE__ */ new Map();
6644
- if (relsBytes !== void 0) for (const rel of byTag$3(parseXml$3(relsBytes, "relationships"), "Relationship")) {
6762
+ if (relsBytes !== void 0) for (const rel of byTag$4(parseXml$4(relsBytes, "relationships"), "Relationship")) {
6645
6763
  const id = rel.getAttribute("Id");
6646
6764
  const target = rel.getAttribute("Target");
6647
- if (id !== null && target !== null) targets.set(id, resolveSheetPath(target));
6765
+ if (id !== null && target !== null) targets.set(id, resolveSheetPath$1(target));
6648
6766
  }
6649
- const sharedBytes = parts.get(SHARED_STRINGS);
6650
- const shared = sharedBytes === void 0 ? [] : byTag$3(parseXml$3(sharedBytes, "shared strings"), "si").map(sharedStringText);
6651
- const dateStyles = readDateStyles(parts.get(STYLES));
6767
+ const sharedBytes = parts.get(SHARED_STRINGS$1);
6768
+ const shared = sharedBytes === void 0 ? [] : byTag$4(parseXml$4(sharedBytes, "shared strings"), "si").map(sharedStringText);
6769
+ const dateStyles = readDateStyles(parts.get(STYLES$1));
6652
6770
  const sheets = [];
6653
- for (const [ordinal, sheet] of byTag$3(workbook, "sheet").entries()) {
6771
+ for (const [ordinal, sheet] of byTag$4(workbook, "sheet").entries()) {
6654
6772
  const name = sheet.getAttribute("name") ?? `Sheet${ordinal + 1}`;
6655
6773
  const relId = sheet.getAttributeNS("http://schemas.openxmlformats.org/officeDocument/2006/relationships", "id") ?? sheet.getAttribute("r:id");
6656
6774
  const path = relId !== null ? targets.get(relId) : void 0;
6657
6775
  const sheetBytes = path !== void 0 ? parts.get(path) : void 0;
6658
- if (path === void 0 || sheetBytes === void 0) fail(`The worksheet "${name}" could not be located inside the workbook.`);
6659
- const rows = sheetRows(parseXml$3(sheetBytes, `worksheet "${name}"`), shared, dateStyles, date1904);
6776
+ if (path === void 0 || sheetBytes === void 0) fail$1(`The worksheet "${name}" could not be located inside the workbook.`);
6777
+ const rows = sheetRows(parseXml$4(sheetBytes, `worksheet "${name}"`), shared, dateStyles, date1904);
6660
6778
  const lines = rows.map(rowLine);
6661
6779
  while (lines.length > 0 && lines[lines.length - 1] === "") {
6662
6780
  lines.pop();
@@ -6677,7 +6795,7 @@ async function readXlsxWorkbook(bytes) {
6677
6795
  /**
6678
6796
  * 从 xlsx 字节里抽出文本,每个工作表一段、段首是表名。
6679
6797
  *
6680
- * @throws WebSkillError 归档损坏、旧版 `.xls`、缺 `xl/workbook.xml`、工作表关系解不开、XML 解析失败
6798
+ * @throws WebSkillError 归档损坏、旧版 `.xls` 转换失败、缺 `xl/workbook.xml`、工作表关系解不开、XML 解析失败
6681
6799
  */
6682
6800
  async function extractXlsxText(bytes) {
6683
6801
  const { sheets } = await readXlsxWorkbook(bytes);
@@ -6738,7 +6856,7 @@ const DEFAULT_BODY_TWIPS = 9360;
6738
6856
  function attr(node, name) {
6739
6857
  for (const item of node.attributes) if (item.name === name || item.localName === name) return item.value;
6740
6858
  }
6741
- function parseXml$2(bytes, what) {
6859
+ function parseXml$3(bytes, what) {
6742
6860
  const doc = new DOMParser().parseFromString(new TextDecoder().decode(bytes), "application/xml");
6743
6861
  if (doc.getElementsByTagName("parsererror").length > 0) throw new WebSkillError("TOOL_EXECUTION_FAILED", `The Word document's ${what} could not be parsed as XML.`);
6744
6862
  return doc;
@@ -6801,10 +6919,10 @@ async function readDocxBlocks(bytes) {
6801
6919
  const files = new Map(entries);
6802
6920
  const main = files.get(DOCUMENT$1);
6803
6921
  if (main === void 0) throw new WebSkillError("TOOL_EXECUTION_FAILED", `This file is not a Word document: it has no ${DOCUMENT$1} entry.`);
6804
- const doc = parseXml$2(main, "body");
6922
+ const doc = parseXml$3(main, "body");
6805
6923
  const targets = /* @__PURE__ */ new Map();
6806
6924
  const rels = files.get(RELS);
6807
- if (rels !== void 0) for (const rel of parseXml$2(rels, "relationships").getElementsByTagName("Relationship")) {
6925
+ if (rels !== void 0) for (const rel of parseXml$3(rels, "relationships").getElementsByTagName("Relationship")) {
6808
6926
  const id = attr(rel, "Id");
6809
6927
  const target = attr(rel, "Target");
6810
6928
  if (id !== void 0 && target !== void 0) targets.set(id, `word/${target.replace(/^\.?\//, "")}`);
@@ -6853,8 +6971,8 @@ const ANCHOR_TAGS = /* @__PURE__ */ new Set([
6853
6971
  /** 绝对锚点没有行号;放在表尾,保证图片不丢。 */
6854
6972
  const AFTER_LAST_ROW = Number.MAX_SAFE_INTEGER;
6855
6973
  const RELATIONSHIPS_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
6856
- const byTag$2 = (scope, name) => [...scope.getElementsByTagNameNS("*", name)];
6857
- function parseXml$1(bytes) {
6974
+ const byTag$3 = (scope, name) => [...scope.getElementsByTagNameNS("*", name)];
6975
+ function parseXml$2(bytes) {
6858
6976
  const doc = new DOMParser().parseFromString(new TextDecoder().decode(bytes), "application/xml");
6859
6977
  return doc.getElementsByTagName("parsererror").length > 0 ? void 0 : doc;
6860
6978
  }
@@ -6875,9 +6993,9 @@ const relsOf = (path) => `${dirOf(path)}_rels/${path.slice(path.lastIndexOf("/")
6875
6993
  function readRels(parts, path) {
6876
6994
  const out = /* @__PURE__ */ new Map();
6877
6995
  const bytes = parts.get(relsOf(path));
6878
- const doc = bytes === void 0 ? void 0 : parseXml$1(bytes);
6996
+ const doc = bytes === void 0 ? void 0 : parseXml$2(bytes);
6879
6997
  if (doc === void 0) return out;
6880
- for (const rel of byTag$2(doc, "Relationship")) {
6998
+ for (const rel of byTag$3(doc, "Relationship")) {
6881
6999
  const id = rel.getAttribute("Id");
6882
7000
  const target = rel.getAttribute("Target");
6883
7001
  if (id !== null && target !== null) out.set(id, resolveRelative$1(dirOf(path), target));
@@ -6885,7 +7003,7 @@ function readRels(parts, path) {
6885
7003
  return out;
6886
7004
  }
6887
7005
  const anchorNumber = (anchor, name) => {
6888
- const node = anchor === void 0 ? void 0 : byTag$2(anchor, name)[0];
7006
+ const node = anchor === void 0 ? void 0 : byTag$3(anchor, name)[0];
6889
7007
  const value = Number(node?.textContent ?? NaN);
6890
7008
  return Number.isInteger(value) && value >= 0 ? value : 0;
6891
7009
  };
@@ -6902,14 +7020,14 @@ function imagesOf(parts, sheetPath) {
6902
7020
  for (const [, target] of sheetRels) {
6903
7021
  if (!target.includes("/drawings/")) continue;
6904
7022
  const drawingBytes = parts.get(target);
6905
- const drawing = drawingBytes === void 0 ? void 0 : parseXml$1(drawingBytes);
7023
+ const drawing = drawingBytes === void 0 ? void 0 : parseXml$2(drawingBytes);
6906
7024
  if (drawing === void 0) continue;
6907
7025
  const drawingRels = readRels(parts, target);
6908
7026
  const anchors = [...drawing.documentElement?.children ?? []].filter((element) => ANCHOR_TAGS.has(element.localName));
6909
7027
  for (const anchor of anchors) {
6910
- const from = byTag$2(anchor, "from")[0];
7028
+ const from = byTag$3(anchor, "from")[0];
6911
7029
  const row = anchor.localName === "absoluteAnchor" || from === void 0 ? AFTER_LAST_ROW : anchorNumber(from, "row");
6912
- const blip = byTag$2(anchor, "blip")[0];
7030
+ const blip = byTag$3(anchor, "blip")[0];
6913
7031
  const relId = blip?.getAttributeNS(RELATIONSHIPS_NS, "embed") ?? blip?.getAttribute("r:embed");
6914
7032
  const path = relId === null || relId === void 0 ? void 0 : drawingRels.get(relId);
6915
7033
  const data = path === void 0 ? void 0 : parts.get(path);
@@ -6926,7 +7044,7 @@ function createXlsxBlockReader() {
6926
7044
  return { read: readXlsxBlocks };
6927
7045
  }
6928
7046
  /**
6929
- * @throws WebSkillError 归档损坏、旧版 `.xls`、缺 `xl/workbook.xml`、工作表关系解不开、XML 解析失败
7047
+ * @throws WebSkillError 归档损坏、旧版 `.xls` 转换失败、缺 `xl/workbook.xml`、工作表关系解不开、XML 解析失败
6930
7048
  * @experimental
6931
7049
  */
6932
7050
  async function readXlsxBlocks(bytes) {
@@ -6951,18 +7069,358 @@ async function readXlsxBlocks(bytes) {
6951
7069
  return { sheets: out };
6952
7070
  }
6953
7071
 
7072
+ //#endregion
7073
+ //#region ../browser/src/document/xlsxStreaming.ts
7074
+ const WORKBOOK = "xl/workbook.xml";
7075
+ const WORKBOOK_RELS = "xl/_rels/workbook.xml.rels";
7076
+ const SHARED_STRINGS = "xl/sharedStrings.xml";
7077
+ const STYLES = "xl/styles.xml";
7078
+ function fail(message) {
7079
+ throw new WebSkillError("TOOL_EXECUTION_FAILED", message);
7080
+ }
7081
+ async function sourceChunks(source) {
7082
+ if (typeof source !== "function") return (async function* () {
7083
+ yield source;
7084
+ })();
7085
+ return await source();
7086
+ }
7087
+ async function* entryChunks(source, path, state) {
7088
+ const output = [];
7089
+ let failure;
7090
+ const unzip = new Unzip();
7091
+ unzip.register(UnzipInflate);
7092
+ unzip.onfile = (file) => {
7093
+ if (file.name !== path) return;
7094
+ state.found = true;
7095
+ file.ondata = (error, chunk) => {
7096
+ if (error !== null) failure = error;
7097
+ else if (failure === void 0) output.push(chunk);
7098
+ };
7099
+ file.start();
7100
+ };
7101
+ try {
7102
+ const chunks = await sourceChunks(source);
7103
+ for await (const chunk of chunks) {
7104
+ unzip.push(chunk, false);
7105
+ yield* output.splice(0);
7106
+ await Promise.resolve();
7107
+ yield* output.splice(0);
7108
+ }
7109
+ unzip.push(/* @__PURE__ */ new Uint8Array(), true);
7110
+ await Promise.resolve();
7111
+ yield* output.splice(0);
7112
+ } catch (e) {
7113
+ failure ??= e;
7114
+ }
7115
+ if (failure !== void 0) {
7116
+ if (failure instanceof WebSkillError) throw failure;
7117
+ throw new WebSkillError("TOOL_EXECUTION_FAILED", `The workbook entry "${path}" could not be read: ${failure instanceof Error ? failure.message : String(failure)}`, failure);
7118
+ }
7119
+ }
7120
+ async function entryBytes(source, path) {
7121
+ const chunks = [];
7122
+ let total = 0;
7123
+ const state = { found: false };
7124
+ for await (const chunk of entryChunks(source, path, state)) {
7125
+ chunks.push(chunk);
7126
+ total += chunk.byteLength;
7127
+ }
7128
+ if (!state.found) return void 0;
7129
+ const out = new Uint8Array(total);
7130
+ let offset = 0;
7131
+ for (const chunk of chunks) {
7132
+ out.set(chunk, offset);
7133
+ offset += chunk.byteLength;
7134
+ }
7135
+ return out;
7136
+ }
7137
+ function parseXml$1(bytes, what) {
7138
+ const doc = new DOMParser().parseFromString(new TextDecoder().decode(bytes), "application/xml");
7139
+ if (doc.getElementsByTagName("parsererror").length > 0) fail(`The workbook ${what} could not be parsed as XML.`);
7140
+ return doc;
7141
+ }
7142
+ const byTag$2 = (scope, name) => [...scope.getElementsByTagNameNS("*", name)];
7143
+ const localName = (value) => {
7144
+ const name = typeof value === "string" ? value : value.name;
7145
+ return name.slice(name.lastIndexOf(":") + 1);
7146
+ };
7147
+ function attribute(tag, name) {
7148
+ const direct = tag.attributes[name];
7149
+ if (direct !== void 0) return direct;
7150
+ const key = Object.keys(tag.attributes).find((candidate) => candidate.endsWith(`:${name}`));
7151
+ return key === void 0 ? null : tag.attributes[key] ?? null;
7152
+ }
7153
+ function newParser(onError) {
7154
+ const parser = new SaxesParser();
7155
+ parser.on("error", onError);
7156
+ return parser;
7157
+ }
7158
+ async function readSharedStrings(source) {
7159
+ const shared = [];
7160
+ let inside = false;
7161
+ let textDepth = 0;
7162
+ let phoneticDepth = 0;
7163
+ let text = "";
7164
+ let parseError;
7165
+ const parser = newParser((error) => {
7166
+ parseError = error;
7167
+ });
7168
+ parser.on("opentag", (tag) => {
7169
+ const name = localName(tag.name);
7170
+ if (name === "si") {
7171
+ inside = true;
7172
+ text = "";
7173
+ } else if (inside && name === "rPh") phoneticDepth += 1;
7174
+ else if (inside && name === "t" && phoneticDepth === 0) textDepth += 1;
7175
+ });
7176
+ parser.on("text", (value) => {
7177
+ if (inside && textDepth > 0 && phoneticDepth === 0) text += value;
7178
+ });
7179
+ parser.on("cdata", (value) => {
7180
+ if (inside && textDepth > 0 && phoneticDepth === 0) text += value;
7181
+ });
7182
+ parser.on("closetag", (nameValue) => {
7183
+ const name = localName(nameValue);
7184
+ if (name === "t" && textDepth > 0) textDepth -= 1;
7185
+ else if (name === "rPh" && phoneticDepth > 0) phoneticDepth -= 1;
7186
+ else if (name === "si" && inside) {
7187
+ shared.push(text);
7188
+ inside = false;
7189
+ text = "";
7190
+ }
7191
+ });
7192
+ const state = { found: false };
7193
+ const decoder = new TextDecoder();
7194
+ for await (const chunk of entryChunks(source, SHARED_STRINGS, state)) parser.write(decoder.decode(chunk, { stream: true }));
7195
+ if (!state.found) return [];
7196
+ parser.write(decoder.decode());
7197
+ parser.close();
7198
+ if (parseError !== void 0) fail(`The workbook shared strings could not be parsed as XML: ${parseError.message}`);
7199
+ return shared;
7200
+ }
7201
+ function scanWorksheet(source, path, shared, dateStyles, date1904) {
7202
+ return (async function* () {
7203
+ const queue = [];
7204
+ let previousRow = 0;
7205
+ let lastYielded = 0;
7206
+ let rowNumber = 0;
7207
+ let cells = [];
7208
+ let nextColumn = 0;
7209
+ let cell;
7210
+ let parseError;
7211
+ const parser = newParser((error) => {
7212
+ parseError = error;
7213
+ });
7214
+ const drain = async function* () {
7215
+ for (const entry of queue.splice(0)) {
7216
+ for (let gap = lastYielded + 1; gap < entry.row; gap += 1) yield {
7217
+ row: gap,
7218
+ cells: []
7219
+ };
7220
+ lastYielded = entry.row;
7221
+ yield entry;
7222
+ }
7223
+ };
7224
+ parser.on("opentag", (tag) => {
7225
+ const name = localName(tag.name);
7226
+ if (name === "row") {
7227
+ const declared = Number(attribute(tag, "r"));
7228
+ rowNumber = Number.isInteger(declared) && declared > 0 ? declared : previousRow + 1;
7229
+ previousRow = rowNumber;
7230
+ cells = [];
7231
+ nextColumn = 0;
7232
+ return;
7233
+ }
7234
+ if (name === "c") {
7235
+ const column = columnIndex(attribute(tag, "r")) ?? nextColumn;
7236
+ cell = {
7237
+ type: attribute(tag, "t"),
7238
+ style: Number(attribute(tag, "s") ?? "0"),
7239
+ column,
7240
+ raw: "",
7241
+ inline: "",
7242
+ valueDepth: 0,
7243
+ inlineDepth: 0,
7244
+ textDepth: 0,
7245
+ phoneticDepth: 0
7246
+ };
7247
+ nextColumn = column + 1;
7248
+ } else if (cell !== void 0 && name === "v") cell.valueDepth += 1;
7249
+ else if (cell !== void 0 && name === "is") cell.inlineDepth += 1;
7250
+ else if (cell !== void 0 && name === "rPh" && cell.inlineDepth > 0) cell.phoneticDepth += 1;
7251
+ else if (cell !== void 0 && name === "t" && cell.inlineDepth > 0 && cell.phoneticDepth === 0) cell.textDepth += 1;
7252
+ });
7253
+ parser.on("text", (value) => {
7254
+ if (cell === void 0) return;
7255
+ if (cell.valueDepth > 0) cell.raw += value;
7256
+ if (cell.textDepth > 0 && cell.phoneticDepth === 0) cell.inline += value;
7257
+ });
7258
+ parser.on("cdata", (value) => {
7259
+ if (cell === void 0) return;
7260
+ if (cell.valueDepth > 0) cell.raw += value;
7261
+ if (cell.textDepth > 0 && cell.phoneticDepth === 0) cell.inline += value;
7262
+ });
7263
+ parser.on("closetag", (nameValue) => {
7264
+ const name = localName(nameValue);
7265
+ if (cell !== void 0 && name === "t" && cell.textDepth > 0) cell.textDepth -= 1;
7266
+ else if (cell !== void 0 && name === "rPh" && cell.phoneticDepth > 0) cell.phoneticDepth -= 1;
7267
+ else if (cell !== void 0 && name === "is" && cell.inlineDepth > 0) cell.inlineDepth -= 1;
7268
+ else if (cell !== void 0 && name === "v" && cell.valueDepth > 0) cell.valueDepth -= 1;
7269
+ else if (name === "c" && cell !== void 0) {
7270
+ let text = cell.raw;
7271
+ if (cell.type === "inlineStr") text = cell.inline;
7272
+ else if (cell.type === "s") {
7273
+ const index = Number(cell.raw);
7274
+ text = Number.isInteger(index) ? shared[index] ?? "" : "";
7275
+ } else if (cell.type === "b") text = cell.raw === "1" ? "TRUE" : "FALSE";
7276
+ else if (cell.type !== "str" && cell.type !== "e" && cell.raw !== "") {
7277
+ const numeric = Number(cell.raw);
7278
+ if (Number.isFinite(numeric) && Number.isInteger(cell.style) && dateStyles[cell.style] === true) text = serialToIso(numeric, date1904);
7279
+ }
7280
+ for (let gap = cells.length; gap < cell.column; gap += 1) cells.push("");
7281
+ if (cells.length > cell.column) cells[cell.column] = text;
7282
+ else cells.push(text);
7283
+ cell = void 0;
7284
+ } else if (name === "row") {
7285
+ while (cells.length > 0 && cells[cells.length - 1] === "") cells.pop();
7286
+ if (cells.length > 0) queue.push({
7287
+ row: rowNumber,
7288
+ cells
7289
+ });
7290
+ cells = [];
7291
+ }
7292
+ });
7293
+ const state = { found: false };
7294
+ const decoder = new TextDecoder();
7295
+ for await (const chunk of entryChunks(source, path, state)) {
7296
+ parser.write(decoder.decode(chunk, { stream: true }));
7297
+ yield* drain();
7298
+ }
7299
+ if (!state.found) fail(`The worksheet entry "${path}" could not be located inside the workbook.`);
7300
+ parser.write(decoder.decode());
7301
+ parser.close();
7302
+ if (parseError !== void 0) fail(`The worksheet "${path}" could not be parsed as XML: ${parseError.message}`);
7303
+ yield* drain();
7304
+ })();
7305
+ }
7306
+ function blank(cells) {
7307
+ return cells.every((value) => value.trim() === "");
7308
+ }
7309
+ function wide(code) {
7310
+ return code >= 19968 && code <= 40959 || code >= 13312 && code <= 19903 || code >= 12288 && code <= 12543 || code >= 44032 && code <= 55215 || code >= 63744 && code <= 64255 || code >= 65280 && code <= 65519;
7311
+ }
7312
+ const MAX_CACHED_ROWS = 5e4;
7313
+ const MAX_CACHED_CELLS = 5e5;
7314
+ async function measure(sheet) {
7315
+ let rowCount = 0;
7316
+ let columnCount = 0;
7317
+ let firstNonEmptyRow = 0;
7318
+ let firstRow = [];
7319
+ let dataRows = 0;
7320
+ let wideChars = 0;
7321
+ let otherChars = sheet.name.length + 8;
7322
+ let cellCount = 0;
7323
+ const rows = [];
7324
+ for await (const entry of sheet.scan()) {
7325
+ rowCount += 1;
7326
+ cellCount += entry.cells.length;
7327
+ if (rowCount <= MAX_CACHED_ROWS && cellCount <= MAX_CACHED_CELLS) rows.push(entry);
7328
+ columnCount = Math.max(columnCount, entry.cells.length);
7329
+ otherChars += Math.max(entry.cells.length - 1, 0) + 1;
7330
+ for (const value of entry.cells) for (let index = 0; index < value.length; index += 1) if (wide(value.charCodeAt(index))) wideChars += 1;
7331
+ else otherChars += 1;
7332
+ if (blank(entry.cells)) continue;
7333
+ if (firstNonEmptyRow === 0) {
7334
+ firstNonEmptyRow = entry.row;
7335
+ firstRow = entry.cells;
7336
+ } else dataRows += 1;
7337
+ }
7338
+ return {
7339
+ meta: {
7340
+ rowCount,
7341
+ columnCount,
7342
+ firstNonEmptyRow,
7343
+ firstRow,
7344
+ dataRows,
7345
+ estimatedTokens: wideChars + Math.ceil(otherChars / 4),
7346
+ textChars: wideChars + otherChars
7347
+ },
7348
+ ...rowCount <= MAX_CACHED_ROWS && cellCount <= MAX_CACHED_CELLS ? { rows } : {}
7349
+ };
7350
+ }
7351
+ function resolveSheetPath(target) {
7352
+ const clean = target.replace(/^\.\//, "");
7353
+ return clean.startsWith("/") ? clean.slice(1) : `xl/${clean}`;
7354
+ }
7355
+ async function readStreamingXlsxWorkbook(input) {
7356
+ const source = await normalizeExcelSource(input);
7357
+ const workbookBytes = await entryBytes(source, WORKBOOK);
7358
+ if (workbookBytes === void 0) fail(`This file is not an Excel workbook: it has no ${WORKBOOK} entry.`);
7359
+ const workbook = parseXml$1(workbookBytes, "index");
7360
+ const workbookPr = byTag$2(workbook, "workbookPr")[0];
7361
+ const date1904 = workbookPr?.getAttribute("date1904") === "1" || workbookPr?.getAttribute("date1904") === "true";
7362
+ const relsBytes = await entryBytes(source, WORKBOOK_RELS);
7363
+ const targets = /* @__PURE__ */ new Map();
7364
+ if (relsBytes !== void 0) for (const rel of byTag$2(parseXml$1(relsBytes, "relationships"), "Relationship")) {
7365
+ const id = rel.getAttribute("Id");
7366
+ const target = rel.getAttribute("Target");
7367
+ if (id !== null && target !== null) targets.set(id, resolveSheetPath(target));
7368
+ }
7369
+ const [shared, dateStyles] = await Promise.all([readSharedStrings(source), entryBytes(source, STYLES).then((bytes) => readDateStyles(bytes))]);
7370
+ const sheets = [];
7371
+ for (const [ordinal, sheet] of byTag$2(workbook, "sheet").entries()) {
7372
+ const name = sheet.getAttribute("name") ?? `Sheet${ordinal + 1}`;
7373
+ const relId = sheet.getAttributeNS("http://schemas.openxmlformats.org/officeDocument/2006/relationships", "id") ?? sheet.getAttribute("r:id");
7374
+ const path = relId !== null ? targets.get(relId) : void 0;
7375
+ if (path === void 0) fail(`The worksheet "${name}" could not be located inside the workbook.`);
7376
+ let metadata;
7377
+ let cachedRows;
7378
+ const scan = () => {
7379
+ const rows = cachedRows;
7380
+ if (rows !== void 0) return (async function* () {
7381
+ yield* rows;
7382
+ })();
7383
+ return scanWorksheet(source, path, shared, dateStyles, date1904);
7384
+ };
7385
+ const entry = {
7386
+ name,
7387
+ scan,
7388
+ meta: () => {
7389
+ metadata ??= measure({
7390
+ name,
7391
+ scan
7392
+ }).then((result) => {
7393
+ cachedRows = result.rows;
7394
+ return result.meta;
7395
+ }).catch((error) => {
7396
+ metadata = void 0;
7397
+ throw error;
7398
+ });
7399
+ return metadata;
7400
+ }
7401
+ };
7402
+ sheets.push(entry);
7403
+ }
7404
+ return {
7405
+ kind: "streaming",
7406
+ sheets
7407
+ };
7408
+ }
7409
+
6954
7410
  //#endregion
6955
7411
  //#region ../browser/src/document/xlsxTable.ts
6956
7412
  /**
6957
- * xlsx → 单元格网格(0.26.0 分册 10 · FR-10.20)。
7413
+ * xls(x) → 单元格网格或可重复扫描的行流;旧版 xls 先在内存中转换成 xlsx。
6958
7414
  *
6959
- * 与 `extractXlsxText` 共用 `readXlsxWorkbook` 的同一份解析(单元格取值、日期换算、空格补位),
6960
- * 所以画像与查询看到的格,和模型从文本里读到的格是同一批。
6961
- * 错误语义同 `extractXlsxText`:旧版 `.xls`、归档损坏、缺 `xl/workbook.xml` 都抛 `WebSkillError`。
7415
+ * `read` 保留既有网格契约;`readStreaming` 用 SAX 逐行解析工作表,不受整包解压的 64 MiB 条目上限约束。
7416
+ * 两条路径使用同一套单元格取值、日期换算与空格补位规则,错误均带 `WebSkillError`。
6962
7417
  * @experimental
6963
7418
  */
6964
7419
  function createXlsxTableReader() {
6965
- return { read: readXlsxTable };
7420
+ return {
7421
+ read: readXlsxTable,
7422
+ readStreaming: readStreamingXlsxWorkbook
7423
+ };
6966
7424
  }
6967
7425
  async function readXlsxTable(bytes) {
6968
7426
  const { sheets } = await readXlsxWorkbook(bytes);