@webskill/sdk 0.20.0 → 0.22.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.
- package/dist/abnfDiagram-O67JEVCF-DtYFjfqO.js +109 -0
- package/dist/agent.d.ts +3 -3
- package/dist/agent.js +1171 -73
- package/dist/{approval-Bbh_Apwg.js → approval-DWQlDPbY.js} +527 -29
- package/dist/arc-8U_6NQWm.js +130 -0
- package/dist/architecture-7GRP2DOG-RTw1wpAq.js +4 -0
- package/dist/architectureDiagram-NJMV4G6O-XmmXrJAK.js +7473 -0
- package/dist/array-DQfpQNY8.js +8 -0
- package/dist/blockDiagram-BEXU5L5S-D4yjnCD1.js +2582 -0
- package/dist/browser.d.ts +1129 -48
- package/dist/browser.js +3562 -278
- package/dist/c4Diagram-YGBWAQC7-BLmA_JSd.js +3668 -0
- package/dist/channel-CZxxLrG7.js +9 -0
- package/dist/chunk-2Q5K7J3B-Ck03gkFR.js +21 -0
- package/dist/chunk-3FUC2YCW-tr6jmbQf.js +530 -0
- package/dist/chunk-5DYCD2WN-Bt97YGTd.js +58 -0
- package/dist/chunk-5VM5RSS4-BBxa48HL.js +21 -0
- package/dist/chunk-6AEJRKK7-JXK89I-O.js +14 -0
- package/dist/chunk-742MDFTN-CPmgCGwC.js +466 -0
- package/dist/chunk-7INBJB4K-C9jq9o66.js +6552 -0
- package/dist/chunk-7M6MHVWA-BrKCWcR1.js +4894 -0
- package/dist/chunk-7PRAP22T-BHED6GVs.js +95 -0
- package/dist/chunk-DUW6YSOI-DoZj78PO.js +19 -0
- package/dist/chunk-FOHPRMQF-Dkbp819z.js +27189 -0
- package/dist/chunk-GNY47TPC-Bpvq1T49.js +87 -0
- package/dist/chunk-GTNCS2PH-CLmh4I2c.js +686 -0
- package/dist/chunk-GWA4HPMP-B6vN3fkL.js +95 -0
- package/dist/chunk-J5ZVWO5B-Bi_Pcobw.js +26 -0
- package/dist/chunk-JWPE2WC7-D1rC79GS.js +12 -0
- package/dist/chunk-LNGE3PJU-D5OIoe3Z.js +2582 -0
- package/dist/chunk-MBY4JIJT-C34L7a4f.js +2276 -0
- package/dist/chunk-NETBCI7D-Cg84k8N8.js +321 -0
- package/dist/chunk-O7XYJQB3-B6K0KOSL.js +8846 -0
- package/dist/chunk-UA2S7LBM-DxHM29_K.js +612 -0
- package/dist/chunk-WEXAMYUT-Ba6jFWhB.js +33 -0
- package/dist/chunk-XXDRQBXY-DPbSy4kg.js +12 -0
- package/dist/chunk-Y2CYZVJY-U3QOMqrW.js +15 -0
- package/dist/chunk-Z7XXMR3K-oLg1h8F3.js +1038 -0
- package/dist/chunk-ZIGJFQKS-DqEFBuHR.js +2215 -0
- package/dist/classDiagram-v2-NBCMYWYE-BqnOWIUt.js +3845 -0
- package/dist/cose-bilkent-JH36ORCC-C0dTAHVK.js +4270 -0
- package/dist/cynefin-OW5HDTMX-B2QvgEQj.js +4 -0
- package/dist/cynefinDiagram-VND7K2PF-yAWc6Z3W.js +479 -0
- package/dist/cytoscape.esm-C64mRh9B.js +28325 -0
- package/dist/dagre-6A5THRUB-D0w2W27v.js +4816 -0
- package/dist/defaultLocale-B_EoOkSF.js +249 -0
- package/dist/diagram-22UHCM2B-DvgzhUzP.js +6115 -0
- package/dist/diagram-3UASUU5V-DWgUB-ef.js +795 -0
- package/dist/diagram-ATOU4E4O-P1olqwIs.js +553 -0
- package/dist/diagram-CDSNMT55-BUlZuLUe.js +402 -0
- package/dist/diagram-MLGK6HIB-6w5qmkGt.js +186 -0
- package/dist/diagram-MPIPVDR6-DEFiSo8o.js +289 -0
- package/dist/dist-UajCDt3n.js +92 -0
- package/dist/ebnfDiagram-ZINNZB2B-CI8liCfR.js +123 -0
- package/dist/elk-276RUBZZ-3SvW6OAK.js +90034 -0
- package/dist/erDiagram-OPXOYQCR-CPTbKhwP.js +2301 -0
- package/dist/{eventTypes-CwciaqCU.js → eventTypes-DcTQXSV1.js} +7 -1
- package/dist/eventmodeling-NTZA5JFV-BlyuqGp0.js +4 -0
- package/dist/flowDiagram-KWPJA3E3-CcUJ6HbV.js +18 -0
- package/dist/ganttDiagram-FUAMR5RP-31UjY0KK.js +3939 -0
- package/dist/geometry-wx5ZTYzi.js +715 -0
- package/dist/gitGraph-4MIJSDKK-ChdVlrk7.js +4 -0
- package/dist/gitGraphDiagram-X574FWY7-DKuxJymB.js +1153 -0
- package/dist/governance.d.ts +9 -3
- package/dist/governance.js +1 -1
- package/dist/graphlib-tk9gQYuN.js +4194 -0
- package/dist/{index-ad11wflI.d.ts → index-BiO9_XRk.d.ts} +2 -2
- package/dist/{index-DQ3-6GFu.d.ts → index-CjwdZQOS.d.ts} +945 -8
- package/dist/{index-BDyXe-a5.d.ts → index-YPd8ZSEa.d.ts} +413 -7
- package/dist/index.d.ts +4 -4
- package/dist/index.js +47 -41
- package/dist/info-A6RAGUB7-CxRAt9S9.js +4 -0
- package/dist/infoDiagram-VRGFBTTK-pRspGfjZ.js +25 -0
- package/dist/init-CMOIWdg-.js +16 -0
- package/dist/ishikawaDiagram-OU5B5YK6-DKerAPAg.js +975 -0
- package/dist/journeyDiagram-ZHPQQLJL-C4MK-YO5.js +1227 -0
- package/dist/kanban-definition-PNTS6WVX-BqsZpMXx.js +1244 -0
- package/dist/katex-BcRRvyh_.js +26465 -0
- package/dist/kind-Dc8x0HWz.js +110 -0
- package/dist/line-LgV8iVMV.js +48 -0
- package/dist/linear-BWKR0Tqd.js +393 -0
- package/dist/linkedDocument-CIuh_Yp0.js +416 -0
- package/dist/mcp.d.ts +2 -2
- package/dist/{memoryArtifactStore-D6dLeY55.js → memoryArtifactStore-Bs7glPqm.js} +14 -0
- package/dist/mermaid-parser.core-pYR5siSx.js +754 -0
- package/dist/mermaid.core-DMtwGCHy.js +1756 -0
- package/dist/mindmap-definition-NLK3R4M7-CXbD-myG.js +1312 -0
- package/dist/node.d.ts +8 -3
- package/dist/node.js +79 -4
- package/dist/{openUiLibrary-b0i7F_oZ.js → openUiLibrary-B7j3rLrm.js} +1 -1
- package/dist/{openUiSpecLang-CLXJEeRU.js → openUiSpecLang-BSYYnjay.js} +1 -1
- package/dist/ordinal-CxA6GHQS.js +84 -0
- package/dist/packet-AYTQ26CC-BOm08cxl.js +4 -0
- package/dist/path-C27Z2Bjv.js +106 -0
- package/dist/pegDiagram-GJSIUBJH-BLGJOewQ.js +115 -0
- package/dist/pie-WAS4IAKB-Dvg7glpk.js +4 -0
- package/dist/pieDiagram-5QR66LMP-laoaV_PB.js +298 -0
- package/dist/processSandboxEntry.js +5 -0
- package/dist/quadrantDiagram-O4NWA36T-Db_wFBkT.js +2236 -0
- package/dist/radar-RG4KPBEZ-BZDaTu06.js +4 -0
- package/dist/railroad-74A4TZTK-C-UhjkOy.js +4 -0
- package/dist/railroad-abnf-HS5TGJTU-dUKVA4OS.js +4 -0
- package/dist/railroad-ebnf-LZEXJU2U-DzQumk18.js +4 -0
- package/dist/railroad-peg-WCYAUIDC-B3vvTWed.js +4 -0
- package/dist/railroadDiagram-XR7U4H2S-BY0sTM4X.js +89 -0
- package/dist/requirementDiagram-PLB6GJNP-3rkQfEhs.js +2474 -0
- package/dist/rough.cjs-AmDd_NQk.js +1395 -0
- package/dist/sandboxWorkerEntry.js +5 -0
- package/dist/sankeyDiagram-IPEJSGJF-CGAJhyG2.js +3578 -0
- package/dist/sequenceDiagram-PO4LG4MO-9FJjFX06.js +5522 -0
- package/dist/sizeCapture-INFHLROL-D8PWxdOn.js +56 -0
- package/dist/{skillVersionStore-B24Jjjry-v2CCU0Xo.d.ts → skillVersionStore-B24Jjjry-BY1H7Krp.d.ts} +1 -1
- package/dist/src-NkhI3UMl.js +2653 -0
- package/dist/stateDiagram-v2-GCMORJYK-B0O3wc1-.js +2715 -0
- package/dist/swimlanes-2SLR337P-D4EOnrpu.js +6637 -0
- package/dist/swimlanesDiagram-TC7HE7FX-BUx29EJ9.js +30 -0
- package/dist/testing.d.ts +2 -1
- package/dist/testing.js +1 -1
- package/dist/timeline-definition-EJHVYXUP-CrgiiB4C.js +1513 -0
- package/dist/treeView-Q6P3EWNA-C1by-Gzc.js +4 -0
- package/dist/treemap-WGGIJYW6-mC2NZ6ad.js +4 -0
- package/dist/{types-Btpdd1y--BcxQ10Fa.d.ts → types-CpDRZ0rA-BM5FI9oN.d.ts} +111 -9
- package/dist/ui-react.d.ts +3 -3
- package/dist/ui-react.js +3718 -77
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui.d.ts +3 -3
- package/dist/ui.js +2 -2
- package/dist/untrustedText-BIaRvPZK.js +16 -0
- package/dist/usecaseDiagram-POWQR4AR-BUt_TQOY.js +14397 -0
- package/dist/vennDiagram-UO4OBE2U-CwXr0wjA.js +2808 -0
- package/dist/wardley-WFR3VGLG-CVSYjq_S.js +4 -0
- package/dist/wardleyDiagram-VNRHLVJA-Cpfd47dQ.js +874 -0
- package/dist/{webSkillApi-CsvA69qk.js → webSkillApi-Dy-Zjv0y.js} +1 -1
- package/dist/{webskillLitCatalog-W06npwej.js → webskillLitCatalog-D5BCiMCU.js} +1 -1
- package/dist/xychartDiagram-PMCCYNJV-e1TeVoc6.js +2704 -0
- package/package.json +6 -5
- package/dist/kind-DxgS8LM-.js +0 -77
- package/dist/linkedDocument-xNXF-z8n.js +0 -94
- package/dist/types-DOJI5YC3.js +0 -90
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { t as WebSkillError } from "./errors-BDZNpC13.js";
|
|
2
|
+
import writeXlsxFile from "write-excel-file/universal";
|
|
3
|
+
|
|
4
|
+
//#region ../runtime/src/tools/types.ts
|
|
5
|
+
/**
|
|
6
|
+
* 参与 `toolResultMaxBytes` 截断的分片类型(FR-23.3)。**判据的单一来源。**
|
|
7
|
+
*
|
|
8
|
+
* 白名单而不是黑名单:黑名单式下新加的分片默认「参与截断」,
|
|
9
|
+
* 漏改一处就会把一份 PDF 截成坏文件、把 docx 文本腰斩,而且没有任何报错。
|
|
10
|
+
* 白名单式下新分片默认**不参与**,要受文本预算约束必须显式加进来。
|
|
11
|
+
* @experimental
|
|
12
|
+
*/
|
|
13
|
+
const TEXT_BUDGETED_CONTENT_TYPES = /* @__PURE__ */ new Set(["text", "json"]);
|
|
14
|
+
/**
|
|
15
|
+
* `file` 分片的上限(FR-23.4),量的是**实际上线的 base64 长度**而不是解码后的字节——
|
|
16
|
+
* provider 的限额算的是请求载荷。
|
|
17
|
+
*
|
|
18
|
+
* 2026-08-14 查证的三家上限:
|
|
19
|
+
* | provider | 上限 | 出处 |
|
|
20
|
+
* | --------- | --------------------------------------- | ----------------------- |
|
|
21
|
+
* | Anthropic | **32 MB**(**整个请求载荷**)、600 页 | PDF support / 请求大小 |
|
|
22
|
+
* | OpenAI | 单文件 50 MB,全部文件合计 50 MB | File inputs / 使用须知 |
|
|
23
|
+
* | Gemini | 50 MB 或 1000 页(内联与 Files API 同) | 文档理解 / 技术详情 |
|
|
24
|
+
*
|
|
25
|
+
* 取最小值 Anthropic 的 32 MB。它是**整个请求**的额度,不是文档单独的额度,
|
|
26
|
+
* 所以再留出余量给系统提示词、catalog(约 34 KB)、历史消息与其余分片。
|
|
27
|
+
* 25 000 KB base64 ≈ 18.75 MB 原始 PDF。取 1024 的整数倍:设置界面按 KB 展示,
|
|
28
|
+
* 十进制的 24 000 000 会显示成 23437.5 这种读不出来的数。
|
|
29
|
+
*/
|
|
30
|
+
const DEFAULT_MAX_DOCUMENT_BYTES = 256e5;
|
|
31
|
+
/**
|
|
32
|
+
* `document-text` 分片的上限(FR-23.4;值由 0.22.0 分册 15 修订)。
|
|
33
|
+
*
|
|
34
|
+
* 它是一道**闸门**,不是配额:抽出来的文本整段进上下文,超了整份拒绝、绝不截断。
|
|
35
|
+
* 抬高它不会让任何一份文档变大,只在「正文落在新旧值之间」时改变行为。
|
|
36
|
+
*
|
|
37
|
+
* 原值 500 KB 的依据是「按英文 4 字节/token 折算 ≈ 128K token,占 200K 窗口的多半」。
|
|
38
|
+
* 那个依据有两处站不住:中文 UTF-8 约 4.5 字节/token,按英文口径设闸对中文偏紧;
|
|
39
|
+
* 而窗口是宿主选的模型决定的,SDK 替宿主假定一个 2026 年初的典型值,
|
|
40
|
+
* 代价由「模型明明装得下却被拒绝」的用户承担——他们从提示里看不出这一点(FR-15.1)。
|
|
41
|
+
*
|
|
42
|
+
* 新值之下,超窗口的失败由 provider 报出而不再由 SDK 提前拦截,这是分册 15 有意的取舍:
|
|
43
|
+
* 误拒浪费的是一次**本可成功**的请求,provider 拒收浪费的是一次**必然失败**的请求。
|
|
44
|
+
* SDK 不做请求体总量预检——那需要知道每个 provider 的窗口与分词口径,又回到「替宿主假定」。
|
|
45
|
+
*
|
|
46
|
+
* 取 1024 的整数倍:设置界面按 KB 展示(5 120 000 = 5000 KB)。
|
|
47
|
+
*/
|
|
48
|
+
const DEFAULT_MAX_DOCUMENT_TEXT_BYTES = 512e4;
|
|
49
|
+
/**
|
|
50
|
+
* 单次 fetchData 结果的字节上限(分册 16,FR-16.6):1000 KB。
|
|
51
|
+
* 结构化数据不是文档,量级差一个数量级;超出**只拒不截**——截断的 JSON 解不出来。
|
|
52
|
+
*/
|
|
53
|
+
const DEFAULT_MAX_DATA_SOURCE_BYTES = 1024e3;
|
|
54
|
+
/**
|
|
55
|
+
* 单个上传文件交给脚本的字节上限(0.15.0 分册 17,FR-17.5):20 MB。
|
|
56
|
+
* 取得比模型图片预算宽:字节不进模型上下文,只进脚本,护的是内存不是 token。
|
|
57
|
+
*/
|
|
58
|
+
const DEFAULT_MAX_UPLOAD_FILE_BYTES = 20 * 1024 * 1024;
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region ../runtime/src/spreadsheet/types.ts
|
|
62
|
+
/** 规模上限(FR-10.7)。越界一律 `VALIDATION_FAILED`,不静默截断 */
|
|
63
|
+
const SPREADSHEET_LIMITS = {
|
|
64
|
+
sheets: 16,
|
|
65
|
+
rowsPerSheet: 1e4,
|
|
66
|
+
columnsPerSheet: 256,
|
|
67
|
+
/** Excel 对单个单元格文本的硬限制 */
|
|
68
|
+
charsPerCell: 32767,
|
|
69
|
+
bytes: 16 * 1024 * 1024,
|
|
70
|
+
/**
|
|
71
|
+
* 单张图片的字节上限(FR-20.6)。在解析后、编码前查,
|
|
72
|
+
* 好过让调用方等整包编码完才被告知超了 16 MB 的总顶。
|
|
73
|
+
*/
|
|
74
|
+
imageBytes: 2 * 1024 * 1024,
|
|
75
|
+
/** 单表图片张数上限;与行数上限同性质,防止一次编码退化成几千次解析 */
|
|
76
|
+
imagesPerSheet: 2e3
|
|
77
|
+
};
|
|
78
|
+
const SPREADSHEET_MIME_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
79
|
+
const SPREADSHEET_EXTENSION = ".xlsx";
|
|
80
|
+
/**
|
|
81
|
+
* 本版**不支持**的 xlsx 能力(FR-10.8,备案 D75)。
|
|
82
|
+
*
|
|
83
|
+
* 导出这张表是为了让上层能如实向用户/模型声明缺什么,而不是笼统写「基础支持」——
|
|
84
|
+
* 与 `XLSX_UNEXTRACTED` 同一条口径。
|
|
85
|
+
*
|
|
86
|
+
* `'images'` 已于 0.22.0 分册 20 移出。`'column-widths'` **仍在**:
|
|
87
|
+
* 那一册只让图片锚点所在的行列拿到够用的尺寸,没有开放通用的行高列宽 API(FR-20.5)。
|
|
88
|
+
*/
|
|
89
|
+
const XLSX_UNSUPPORTED = [
|
|
90
|
+
"cell-styles",
|
|
91
|
+
"fonts-and-colors",
|
|
92
|
+
"borders",
|
|
93
|
+
"formulas",
|
|
94
|
+
"merged-cells",
|
|
95
|
+
"charts",
|
|
96
|
+
"comments",
|
|
97
|
+
"date-and-time-cell-types",
|
|
98
|
+
"column-widths",
|
|
99
|
+
"frozen-panes",
|
|
100
|
+
"auto-filter",
|
|
101
|
+
"incremental-edit-of-existing-file"
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region ../runtime/src/spreadsheet/encodeSpreadsheet.ts
|
|
106
|
+
/** Excel 不允许出现在工作表名里的字符 */
|
|
107
|
+
const ILLEGAL_SHEET_NAME_CHARS = /[:\\/?*[\]]/;
|
|
108
|
+
const MAX_SHEET_NAME_LENGTH = 31;
|
|
109
|
+
/**
|
|
110
|
+
* 图片按 96 dpi 嵌入:规格里的 `width`/`height` 是屏幕像素,
|
|
111
|
+
* 96 让它们一比一落到 Excel 的默认显示比例上,也让下面两个换算用同一个基准。
|
|
112
|
+
*/
|
|
113
|
+
const IMAGE_DPI = 96;
|
|
114
|
+
/** px → pt(1 pt = 1/72 inch,96 dpi 下即 ×0.75),再留一点余量免得图片贴着行线 */
|
|
115
|
+
const PT_PER_PX = .75;
|
|
116
|
+
const ROW_PADDING_PT = 4;
|
|
117
|
+
/**
|
|
118
|
+
* px → Excel 列宽(单位是默认字体的字符宽度)。
|
|
119
|
+
* Excel 的换算是 `px = width × 7 + 5`,这里取它的逆再加一格余量。
|
|
120
|
+
*/
|
|
121
|
+
const COLUMN_CHAR_PX = 7;
|
|
122
|
+
const COLUMN_PADDING_PX = 5;
|
|
123
|
+
function invalid(message) {
|
|
124
|
+
return new WebSkillError("VALIDATION_FAILED", message);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 把一个 `SpreadsheetCell` 映射成库的输入。
|
|
128
|
+
*
|
|
129
|
+
* **必须显式传 `type`**:裸值会让库自己推断类型,虽然实测 `'0123'` 恰好推断正确,
|
|
130
|
+
* 但那是库的实现细节不是它的契约,而 FR-10.2a 要求「不做智能识别」。
|
|
131
|
+
*
|
|
132
|
+
* `''` 在这里归一成 `null`(DV-3):xlsx 里「空文本」与「空单元格」产出的字节
|
|
133
|
+
* 完全相同,归一让下游只有一条路径。
|
|
134
|
+
*/
|
|
135
|
+
function toCell(value) {
|
|
136
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
137
|
+
if (typeof value === "string") return {
|
|
138
|
+
value,
|
|
139
|
+
type: String
|
|
140
|
+
};
|
|
141
|
+
if (typeof value === "number") return {
|
|
142
|
+
value,
|
|
143
|
+
type: Number
|
|
144
|
+
};
|
|
145
|
+
return {
|
|
146
|
+
value,
|
|
147
|
+
type: Boolean
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function assertSheetName(name, index, seen) {
|
|
151
|
+
if (typeof name !== "string" || name === "") throw invalid(`Sheet ${index + 1}: "name" must be a non-empty string.`);
|
|
152
|
+
if (name.length > MAX_SHEET_NAME_LENGTH) throw invalid(`Sheet "${name}" has a name of ${name.length} characters (max ${MAX_SHEET_NAME_LENGTH}); Excel would refuse to open the file.`);
|
|
153
|
+
if (ILLEGAL_SHEET_NAME_CHARS.test(name)) throw invalid(`Sheet "${name}" has a name containing one of : \\ / ? * [ ], which Excel does not allow.`);
|
|
154
|
+
const key = name.toLowerCase();
|
|
155
|
+
const previous = seen.get(key);
|
|
156
|
+
if (previous !== void 0) throw invalid(`Sheet "${name}" duplicates the name of sheet ${previous + 1}; Excel requires unique sheet names.`);
|
|
157
|
+
seen.set(key, index);
|
|
158
|
+
return name;
|
|
159
|
+
}
|
|
160
|
+
function assertCell(value, sheetName, row, column) {
|
|
161
|
+
if (value === null || value === void 0) return;
|
|
162
|
+
if (typeof value === "number") {
|
|
163
|
+
if (!Number.isFinite(value)) throw invalid(`Sheet "${sheetName}" row ${row} column ${column}: value must be a finite number.`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (typeof value === "string") {
|
|
167
|
+
if (value.length > SPREADSHEET_LIMITS.charsPerCell) throw invalid(`Sheet "${sheetName}" row ${row} column ${column}: text is ${value.length} characters (max ${SPREADSHEET_LIMITS.charsPerCell}).`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (typeof value === "boolean") return;
|
|
171
|
+
throw invalid(`Sheet "${sheetName}" row ${row} column ${column}: cell must be a string, number, boolean or null (got ${typeof value}).`);
|
|
172
|
+
}
|
|
173
|
+
function positiveInt(value) {
|
|
174
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* 图片的结构与定位校验(FR-20.4 / FR-20.9)。**只查规格,不碰字节**——
|
|
178
|
+
* 字节要等解析出口给,而那是异步的,不能混进这个同步的一次查完里。
|
|
179
|
+
*
|
|
180
|
+
* 行号上界是「表头 + 数据」的总行数:超出去就没有那一行可以挂行高了。
|
|
181
|
+
* 列号则**允许超出实际数据列**——把截图列排在数据右边是常见排布,那里本来就没有单元格。
|
|
182
|
+
*/
|
|
183
|
+
function assertImages(images, sheetName, totalRows) {
|
|
184
|
+
if (!Array.isArray(images)) throw invalid(`Sheet "${sheetName}": "images" must be an array when present.`);
|
|
185
|
+
if (images.length > SPREADSHEET_LIMITS.imagesPerSheet) throw invalid(`Sheet "${sheetName}" has ${images.length} images (max ${SPREADSHEET_LIMITS.imagesPerSheet}).`);
|
|
186
|
+
images.forEach((image, index) => {
|
|
187
|
+
const at = `Sheet "${sheetName}" image ${index + 1}`;
|
|
188
|
+
if (typeof image !== "object" || image === null) throw invalid(`${at}: must be an object.`);
|
|
189
|
+
const { ref, anchor, width, height, alt } = image;
|
|
190
|
+
if (typeof ref !== "string" || ref === "") throw invalid(`${at}: "ref" must be a non-empty string.`);
|
|
191
|
+
if (typeof anchor !== "object" || anchor === null) throw invalid(`${at} (ref "${ref}"): "anchor" must be an object with "row" and "column".`);
|
|
192
|
+
const { row, column } = anchor;
|
|
193
|
+
if (!positiveInt(row)) throw invalid(`${at} (ref "${ref}"): "anchor.row" must be a positive integer (rows start at 1).`);
|
|
194
|
+
if (!positiveInt(column)) throw invalid(`${at} (ref "${ref}"): "anchor.column" must be a positive integer (columns start at 1).`);
|
|
195
|
+
if (row > totalRows) throw invalid(`${at} (ref "${ref}"): "anchor.row" is ${row} but the sheet has ${totalRows} row(s), header included.`);
|
|
196
|
+
if (column > SPREADSHEET_LIMITS.columnsPerSheet) throw invalid(`${at} (ref "${ref}"): "anchor.column" is ${column} (max ${SPREADSHEET_LIMITS.columnsPerSheet}).`);
|
|
197
|
+
if (typeof width !== "number" || !Number.isFinite(width) || width <= 0) throw invalid(`${at} (ref "${ref}"): "width" must be a positive finite number of pixels.`);
|
|
198
|
+
if (typeof height !== "number" || !Number.isFinite(height) || height <= 0) throw invalid(`${at} (ref "${ref}"): "height" must be a positive finite number of pixels.`);
|
|
199
|
+
if (alt !== void 0 && typeof alt !== "string") throw invalid(`${at} (ref "${ref}"): "alt" must be a string when present.`);
|
|
200
|
+
});
|
|
201
|
+
return images;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* 全量校验(FR-10.7)。**在映射与编码之前一次查完**,抛第一个越界项的定位。
|
|
205
|
+
*
|
|
206
|
+
* 不做边查边编码:编码到一半才失败会留下一个已经开始的 Blob,
|
|
207
|
+
* 而且用户看到的错误顺序取决于内部遍历顺序,不稳定。
|
|
208
|
+
*/
|
|
209
|
+
function assertSpec(spec) {
|
|
210
|
+
const sheets = spec?.sheets;
|
|
211
|
+
if (!Array.isArray(sheets) || sheets.length === 0) throw invalid("Spreadsheet must contain at least one sheet.");
|
|
212
|
+
if (sheets.length > SPREADSHEET_LIMITS.sheets) throw invalid(`Spreadsheet has ${sheets.length} sheets (max ${SPREADSHEET_LIMITS.sheets}).`);
|
|
213
|
+
const seen = /* @__PURE__ */ new Map();
|
|
214
|
+
sheets.forEach((sheet, index) => {
|
|
215
|
+
const name = assertSheetName(sheet?.name, index, seen);
|
|
216
|
+
const { columns, rows } = sheet;
|
|
217
|
+
if (columns !== void 0 && !Array.isArray(columns)) throw invalid(`Sheet "${name}": "columns" must be an array of header strings when present.`);
|
|
218
|
+
if (!Array.isArray(rows)) throw invalid(`Sheet "${name}": "rows" must be an array of rows.`);
|
|
219
|
+
if (rows.length > SPREADSHEET_LIMITS.rowsPerSheet) throw invalid(`Sheet "${name}" exceeds the row limit: ${rows.length} rows (max ${SPREADSHEET_LIMITS.rowsPerSheet}).`);
|
|
220
|
+
const widest = rows.reduce((max, row) => Math.max(max, Array.isArray(row) ? row.length : 0), columns?.length ?? 0);
|
|
221
|
+
if (widest > SPREADSHEET_LIMITS.columnsPerSheet) throw invalid(`Sheet "${name}" exceeds the column limit: ${widest} columns (max ${SPREADSHEET_LIMITS.columnsPerSheet}).`);
|
|
222
|
+
const headerOffset = columns === void 0 ? 0 : 1;
|
|
223
|
+
columns?.forEach((header, column) => assertCell(header, name, 1, column + 1));
|
|
224
|
+
rows.forEach((row, rowIndex) => {
|
|
225
|
+
if (!Array.isArray(row)) throw invalid(`Sheet "${name}" row ${rowIndex + 1 + headerOffset}: row must be an array of cells.`);
|
|
226
|
+
row.forEach((cell, column) => assertCell(cell, name, rowIndex + 1 + headerOffset, column + 1));
|
|
227
|
+
});
|
|
228
|
+
if (sheet.images !== void 0) assertImages(sheet.images, name, headerOffset + rows.length);
|
|
229
|
+
});
|
|
230
|
+
return sheets;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* 表头行。加粗写死在这里,**不向调用方暴露任何样式入参**——
|
|
234
|
+
* D75 延后的是样式 API,不是「产出的文件里一个样式属性都不许有」:
|
|
235
|
+
* 表头不加粗的表格在 Excel 里看不出哪行是表头。
|
|
236
|
+
*/
|
|
237
|
+
function headerRow(columns) {
|
|
238
|
+
return columns.map((header) => {
|
|
239
|
+
const cell = toCell(header);
|
|
240
|
+
return cell === null ? null : {
|
|
241
|
+
...cell,
|
|
242
|
+
fontWeight: "bold"
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* 引用换字节(FR-20.3 / FR-20.6 / FR-20.9)。
|
|
248
|
+
*
|
|
249
|
+
* **顺序解析而非并行**:并行时先失败的是哪一张取决于各自的解析耗时,
|
|
250
|
+
* 同一份坏规格两次跑出来的错误信息会不一样。与 `assertSpec` 那条
|
|
251
|
+
* 「错误顺序必须稳定」是同一条理由。
|
|
252
|
+
*/
|
|
253
|
+
async function resolveImages(sheetName, images, resolve) {
|
|
254
|
+
const resolved = [];
|
|
255
|
+
for (const [index, image] of images.entries()) {
|
|
256
|
+
const at = `Sheet "${sheetName}" image ${index + 1} (ref "${image.ref}") at row ${image.anchor.row} column ${image.anchor.column}`;
|
|
257
|
+
const found = await resolve(image.ref);
|
|
258
|
+
if (found === void 0) throw invalid(`${at}: the reference could not be resolved to image bytes.`);
|
|
259
|
+
if (found.bytes.length === 0) throw invalid(`${at}: the reference resolved to zero bytes.`);
|
|
260
|
+
if (found.bytes.length > SPREADSHEET_LIMITS.imageBytes) throw invalid(`${at}: the image is ${found.bytes.length} bytes (max ${SPREADSHEET_LIMITS.imageBytes}).`);
|
|
261
|
+
const buffer = new ArrayBuffer(found.bytes.byteLength);
|
|
262
|
+
new Uint8Array(buffer).set(found.bytes);
|
|
263
|
+
resolved.push({
|
|
264
|
+
content: new Blob([buffer], { type: found.contentType }),
|
|
265
|
+
contentType: found.contentType,
|
|
266
|
+
width: image.width,
|
|
267
|
+
height: image.height,
|
|
268
|
+
dpi: IMAGE_DPI,
|
|
269
|
+
anchor: {
|
|
270
|
+
row: image.anchor.row,
|
|
271
|
+
column: image.anchor.column
|
|
272
|
+
},
|
|
273
|
+
...image.alt === void 0 ? {} : { description: image.alt }
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
return resolved;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* 撑开锚点所在的行与列(FR-20.5)。
|
|
280
|
+
*
|
|
281
|
+
* xlsx 里的图片是浮动对象,不受行高列宽约束:默认行高约 20 px 时,
|
|
282
|
+
* 一张 120 px 的图会压住下面六七行数据。所以「支持图片」与「给锚点行列定尺寸」
|
|
283
|
+
* 是同一件事的两半。
|
|
284
|
+
*
|
|
285
|
+
* 同一行/列被多张图锚定时取能容下所有图的那个尺寸。
|
|
286
|
+
* 返回值是库的 `SheetOptions.columns`(列宽),行高则就地写进 `data`。
|
|
287
|
+
*/
|
|
288
|
+
function applyImageSizing(data, images) {
|
|
289
|
+
const rowPx = /* @__PURE__ */ new Map();
|
|
290
|
+
const columnPx = /* @__PURE__ */ new Map();
|
|
291
|
+
for (const { anchor, width, height } of images) {
|
|
292
|
+
rowPx.set(anchor.row, Math.max(rowPx.get(anchor.row) ?? 0, height));
|
|
293
|
+
columnPx.set(anchor.column, Math.max(columnPx.get(anchor.column) ?? 0, width));
|
|
294
|
+
}
|
|
295
|
+
for (const [row, px] of rowPx) {
|
|
296
|
+
const line = data[row - 1];
|
|
297
|
+
if (line === void 0) continue;
|
|
298
|
+
const height = Math.ceil(px * PT_PER_PX) + ROW_PADDING_PT;
|
|
299
|
+
const first = line[0];
|
|
300
|
+
line[0] = first === null || first === void 0 ? { height } : {
|
|
301
|
+
...first,
|
|
302
|
+
height
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
const widest = Math.max(...columnPx.keys());
|
|
306
|
+
return Array.from({ length: widest }, (_, index) => {
|
|
307
|
+
const px = columnPx.get(index + 1);
|
|
308
|
+
return px === void 0 ? {} : { width: Math.ceil((px + COLUMN_PADDING_PX) / COLUMN_CHAR_PX) + 1 };
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* 把表格规格编码为 xlsx 字节。
|
|
313
|
+
*
|
|
314
|
+
* 纯函数——不认识产物、run 与工具,因此工具、脚本能力、测试三个消费面共用同一份
|
|
315
|
+
* 实现,AC-10.8「双消费面逐字节相同」是结构性成立的。
|
|
316
|
+
*
|
|
317
|
+
* `resolveImage` 是图片引用的解析出口(0.22.0 分册 20 FR-20.3)。它同样不破坏纯函数性:
|
|
318
|
+
* 给定相同的规格与相同的解析结果,输出逐字节相同。规格里有图片却没给它,是
|
|
319
|
+
* `VALIDATION_FAILED` 而不是静默出一份缺图的文件(FR-20.9)。
|
|
320
|
+
*/
|
|
321
|
+
async function encodeSpreadsheet(spec, resolveImage) {
|
|
322
|
+
const sheets = assertSpec(spec);
|
|
323
|
+
const payload = [];
|
|
324
|
+
for (const sheet of sheets) {
|
|
325
|
+
const data = [...sheet.columns === void 0 ? [] : [headerRow(sheet.columns)], ...sheet.rows.map((row) => row.map(toCell))];
|
|
326
|
+
const images = sheet.images ?? [];
|
|
327
|
+
if (images.length === 0) {
|
|
328
|
+
payload.push({
|
|
329
|
+
sheet: sheet.name,
|
|
330
|
+
data
|
|
331
|
+
});
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (resolveImage === void 0) throw invalid(`Sheet "${sheet.name}" has ${images.length} image(s) but no image resolver was provided; pass one to encodeSpreadsheet() or remove the images from the spec.`);
|
|
335
|
+
payload.push({
|
|
336
|
+
sheet: sheet.name,
|
|
337
|
+
data,
|
|
338
|
+
images: await resolveImages(sheet.name, images, resolveImage),
|
|
339
|
+
columns: applyImageSizing(data, images)
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
const blob = await writeXlsxFile(payload).toBlob();
|
|
343
|
+
const bytes = new Uint8Array(await blob.arrayBuffer());
|
|
344
|
+
if (bytes.length > SPREADSHEET_LIMITS.bytes) throw invalid(`The encoded spreadsheet is ${bytes.length} bytes (max ${SPREADSHEET_LIMITS.bytes}); reduce the number of rows, sheets or images.`);
|
|
345
|
+
return bytes;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
//#endregion
|
|
349
|
+
//#region ../runtime/src/tools/linkedDocument.ts
|
|
350
|
+
const READ_LINKED_DOCUMENT_TOOL_NAME = "read_linked_document";
|
|
351
|
+
/**
|
|
352
|
+
* 能直接给模型用的 MIME。其余一律拒绝并列出这份清单——
|
|
353
|
+
* 「尽力而为地猜格式」会让模型收到乱码却以为读成功了。
|
|
354
|
+
*/
|
|
355
|
+
const SUPPORTED_DOCUMENT_MIME = {
|
|
356
|
+
pdf: "application/pdf",
|
|
357
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
358
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
359
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
360
|
+
};
|
|
361
|
+
/**
|
|
362
|
+
* 读到就**只留字节**的类型(0.22.0 FR-19.9b):不抽文本,也不作为 `file` 分片外发,
|
|
363
|
+
* 模型只拿到取件号。DWG 是第一例——17 MB 图纸 base64 进请求体既烧穿预算又毫无意义,
|
|
364
|
+
* 几何该交给解析器。服务端常把 `.dwg` 发成 `application/octet-stream`,所以按扩展名兜底。
|
|
365
|
+
*/
|
|
366
|
+
const BYTES_ONLY_DOCUMENT_MIME = [
|
|
367
|
+
"image/vnd.dwg",
|
|
368
|
+
"application/acad",
|
|
369
|
+
"application/x-acad",
|
|
370
|
+
"application/dwg",
|
|
371
|
+
"application/x-dwg",
|
|
372
|
+
"drawing/dwg"
|
|
373
|
+
];
|
|
374
|
+
const BYTES_ONLY_MIME_SET = new Set(BYTES_ONLY_DOCUMENT_MIME);
|
|
375
|
+
/** 只按字节留存的判定。`url` 只在 MIME 说不清时用到 @experimental */
|
|
376
|
+
function isBytesOnlyDocument(mime, url) {
|
|
377
|
+
if (BYTES_ONLY_MIME_SET.has(mime)) return true;
|
|
378
|
+
if (mime !== "" && mime !== "application/octet-stream") return false;
|
|
379
|
+
return ((url.split(/[?#]/)[0] ?? "").split(".").pop() ?? "").toLowerCase() === "dwg";
|
|
380
|
+
}
|
|
381
|
+
/** 引擎侧固定流程(§2.3)用到的错误文案,集中一处便于判据引用 */
|
|
382
|
+
const UNSUPPORTED_DOCUMENT_MESSAGE = `Only PDF (${SUPPORTED_DOCUMENT_MIME.pdf}), Word (${SUPPORTED_DOCUMENT_MIME.docx}), Excel (${SUPPORTED_DOCUMENT_MIME.xlsx}), PowerPoint (${SUPPORTED_DOCUMENT_MIME.pptx}) and plain text documents can be read.`;
|
|
383
|
+
const READ_LINKED_DOCUMENT_INPUT_SCHEMA = {
|
|
384
|
+
type: "object",
|
|
385
|
+
properties: { url: {
|
|
386
|
+
type: "string",
|
|
387
|
+
description: "The href of a link that appeared in a perceive_page result."
|
|
388
|
+
} },
|
|
389
|
+
required: ["url"]
|
|
390
|
+
};
|
|
391
|
+
/**
|
|
392
|
+
* 内建工具:读页面链接指向的文档。
|
|
393
|
+
*
|
|
394
|
+
* **始终注册**(只要宿主装配了 reader):docx / xlsx 与纯文本这条路对所有模型成立。
|
|
395
|
+
* PDF 目标在模型不支持文档时**取数后拒绝**,而不是入口就拦 ——
|
|
396
|
+
* 入口拦会误伤 docx,它抽成文本后根本不需要文档能力。
|
|
397
|
+
*/
|
|
398
|
+
const READ_LINKED_DOCUMENT_TOOL = {
|
|
399
|
+
name: READ_LINKED_DOCUMENT_TOOL_NAME,
|
|
400
|
+
description: "Read a document linked from the current page. Pass the exact \"href\" from a perceive_page result. Cross-origin documents need the user to confirm each time.",
|
|
401
|
+
inputSchema: READ_LINKED_DOCUMENT_INPUT_SCHEMA,
|
|
402
|
+
source: "builtin"
|
|
403
|
+
};
|
|
404
|
+
/** 二进制转 base64;`btoa` 只吃 latin1,必须逐字节喂而不是先 decode 成字符串 */
|
|
405
|
+
function toBase64(bytes) {
|
|
406
|
+
let binary = "";
|
|
407
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
408
|
+
return btoa(binary);
|
|
409
|
+
}
|
|
410
|
+
/** `toBase64` 的逆;分片里的文档要交回抽取器时用(0.13.0 FR-22.3) */
|
|
411
|
+
function fromBase64(data) {
|
|
412
|
+
return Uint8Array.from(atob(data), (char) => char.charCodeAt(0));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
//#endregion
|
|
416
|
+
export { DEFAULT_MAX_UPLOAD_FILE_BYTES as _, UNSUPPORTED_DOCUMENT_MESSAGE as a, toBase64 as c, SPREADSHEET_LIMITS as d, SPREADSHEET_MIME_TYPE as f, DEFAULT_MAX_DOCUMENT_TEXT_BYTES as g, DEFAULT_MAX_DOCUMENT_BYTES as h, SUPPORTED_DOCUMENT_MIME as i, encodeSpreadsheet as l, DEFAULT_MAX_DATA_SOURCE_BYTES as m, READ_LINKED_DOCUMENT_TOOL as n, fromBase64 as o, XLSX_UNSUPPORTED as p, READ_LINKED_DOCUMENT_TOOL_NAME as r, isBytesOnlyDocument as s, BYTES_ONLY_DOCUMENT_MIME as t, SPREADSHEET_EXTENSION as u, TEXT_BUDGETED_CONTENT_TYPES as v };
|
package/dist/mcp.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { X as JsonSchema, _ as LlmToolSpec, _t as SkillDocument, ht as SkillCatalogEntry } from "./types-CpDRZ0rA-BM5FI9oN.js";
|
|
2
|
+
import { B as ExternalToolSource, Hr as mergeCatalogEntries, Mn as ToolDisclosure, Pn as ToolResult, z as ExternalSkillProvider } from "./index-YPd8ZSEa.js";
|
|
3
3
|
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
4
4
|
import { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
import { t as WebSkillError } from "./errors-BDZNpC13.js";
|
|
2
|
+
|
|
1
3
|
//#region ../runtime/src/artifacts/memoryArtifactStore.ts
|
|
4
|
+
function key(runId, path) {
|
|
5
|
+
return `${runId}\u0000${path}`;
|
|
6
|
+
}
|
|
2
7
|
/** 内存 ArtifactStore:测试与浏览器降级用;索引随进程生命周期存在 */
|
|
3
8
|
var MemoryArtifactStore = class {
|
|
4
9
|
#byRun = /* @__PURE__ */ new Map();
|
|
10
|
+
/** 字节也得留着,否则 `readArtifact` 无从实现(FR-20.8)。key = runId + \0 + path */
|
|
11
|
+
#bytes = /* @__PURE__ */ new Map();
|
|
5
12
|
#seq = 0;
|
|
6
13
|
async createTextArtifact(input) {
|
|
14
|
+
this.#bytes.set(key(input.runId, input.path), new TextEncoder().encode(input.content));
|
|
7
15
|
return this.#record({
|
|
8
16
|
runId: input.runId,
|
|
9
17
|
path: input.path,
|
|
@@ -14,6 +22,7 @@ var MemoryArtifactStore = class {
|
|
|
14
22
|
});
|
|
15
23
|
}
|
|
16
24
|
async createBinaryArtifact(input) {
|
|
25
|
+
this.#bytes.set(key(input.runId, input.path), input.content.slice());
|
|
17
26
|
return this.#record({
|
|
18
27
|
runId: input.runId,
|
|
19
28
|
path: input.path,
|
|
@@ -26,6 +35,11 @@ var MemoryArtifactStore = class {
|
|
|
26
35
|
async listArtifacts(runId) {
|
|
27
36
|
return [...this.#byRun.get(runId) ?? []];
|
|
28
37
|
}
|
|
38
|
+
async readArtifact(runId, path) {
|
|
39
|
+
const found = this.#bytes.get(key(runId, path));
|
|
40
|
+
if (found === void 0) throw new WebSkillError("FS_NOT_FOUND", `Artifact "${path}" not found in run ${runId}`);
|
|
41
|
+
return found;
|
|
42
|
+
}
|
|
29
43
|
#record(input) {
|
|
30
44
|
const artifact = {
|
|
31
45
|
id: `art-${++this.#seq}`,
|