@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
package/dist/agent.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { n as messageOf, t as WebSkillError } from "./errors-BDZNpC13.js";
|
|
2
2
|
import { b as parseSkillMarkdown, y as isValidSkillName } from "./skill-CAJMsLod.js";
|
|
3
3
|
import { t as assertRemoteUrlAllowed } from "./urlSafety-CiSuCJvX.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { n as sanitizeUntrustedLine, t as UNTRUSTED_LINE_LIMIT } from "./untrustedText-BIaRvPZK.js";
|
|
5
|
+
import { d as extractedDocumentFormat, f as formatAttachmentText, u as classifyAttachment } from "./kind-Dc8x0HWz.js";
|
|
6
|
+
import { c as toBase64, d as SPREADSHEET_LIMITS, f as SPREADSHEET_MIME_TYPE, l as encodeSpreadsheet, m as DEFAULT_MAX_DATA_SOURCE_BYTES, u as SPREADSHEET_EXTENSION } from "./linkedDocument-CIuh_Yp0.js";
|
|
7
|
+
import { C as DOCUMENT_READ_BUDGET, D as toFrameScopes, E as toActionFrameScopes, O as frameLabel, S as sniffDocumentFormat, T as PAGE_DOCUMENT_REF, _ as defersImage, a as columnWidthToPixels, b as isPdfTextTrustworthy, c as isRangeTopLeft, d as rangeContains, f as readImageSize, g as asReadMode, h as createDocumentToolSource, i as MAX_DIGIT_WIDTH, k as frameSteps, l as parseCellAddress, m as READ_DOCUMENT_TOOL, n as EMU_PER_POINT, o as fitWithin, p as PDF_LARGE_IMAGE_RATIO, r as EMU_PER_TWIP, s as formatCellAddress, t as EMU_PER_PIXEL, u as parseCellRange, v as takesImage, w as PAGE_ACTION_KINDS, x as measurePdfTextQuality, y as PDF_GARBLED_RATIO } from "./geometry-wx5ZTYzi.js";
|
|
7
8
|
|
|
8
9
|
//#region ../agent/src/todo/store.ts
|
|
9
10
|
const STATUSES = [
|
|
@@ -129,7 +130,7 @@ const STATUS_ENUM = [
|
|
|
129
130
|
"completed"
|
|
130
131
|
];
|
|
131
132
|
const TODO_TOOL_DESCRIPTION = "Maintain the visible task list for a multi-step request. The rules are in the system prompt.";
|
|
132
|
-
const INPUT_SCHEMA$
|
|
133
|
+
const INPUT_SCHEMA$6 = {
|
|
133
134
|
type: "object",
|
|
134
135
|
properties: {
|
|
135
136
|
action: {
|
|
@@ -182,7 +183,7 @@ const INPUT_SCHEMA$4 = {
|
|
|
182
183
|
required: ["action"],
|
|
183
184
|
additionalProperties: false
|
|
184
185
|
};
|
|
185
|
-
function toolError$
|
|
186
|
+
function toolError$8(code, message) {
|
|
186
187
|
return {
|
|
187
188
|
ok: false,
|
|
188
189
|
content: [],
|
|
@@ -231,15 +232,15 @@ function createTodoToolSource(options = {}) {
|
|
|
231
232
|
try {
|
|
232
233
|
if (action === "create") {
|
|
233
234
|
const items = parseItems(args["items"]);
|
|
234
|
-
if (typeof items === "string") return Promise.resolve(toolError$
|
|
235
|
+
if (typeof items === "string") return Promise.resolve(toolError$8("VALIDATION_FAILED", items));
|
|
235
236
|
const list = store.create(items);
|
|
236
237
|
return Promise.resolve(ok(events, progressText(list.items)));
|
|
237
238
|
}
|
|
238
239
|
if (action === "update") {
|
|
239
240
|
const id = args["id"];
|
|
240
241
|
const status = args["status"];
|
|
241
|
-
if (typeof id !== "string") return Promise.resolve(toolError$
|
|
242
|
-
if (status !== void 0 && typeof status !== "string") return Promise.resolve(toolError$
|
|
242
|
+
if (typeof id !== "string") return Promise.resolve(toolError$8("VALIDATION_FAILED", "The \"id\" argument is required for action \"update\""));
|
|
243
|
+
if (status !== void 0 && typeof status !== "string") return Promise.resolve(toolError$8("VALIDATION_FAILED", "The \"status\" argument must be a string"));
|
|
243
244
|
const title = args["title"];
|
|
244
245
|
const list = store.update(id, {
|
|
245
246
|
...status !== void 0 ? { status } : {},
|
|
@@ -251,10 +252,10 @@ function createTodoToolSource(options = {}) {
|
|
|
251
252
|
store.clear();
|
|
252
253
|
return Promise.resolve(ok(events, progressText([])));
|
|
253
254
|
}
|
|
254
|
-
return Promise.resolve(toolError$
|
|
255
|
+
return Promise.resolve(toolError$8("VALIDATION_FAILED", "The \"action\" argument must be one of create, update, clear"));
|
|
255
256
|
} catch (e) {
|
|
256
257
|
const code = e instanceof Error && "code" in e ? String(e.code) : "VALIDATION_FAILED";
|
|
257
|
-
return Promise.resolve(toolError$
|
|
258
|
+
return Promise.resolve(toolError$8(code, e instanceof Error ? e.message : String(e)));
|
|
258
259
|
} finally {
|
|
259
260
|
unsubscribe();
|
|
260
261
|
}
|
|
@@ -266,7 +267,7 @@ function createTodoToolSource(options = {}) {
|
|
|
266
267
|
listToolSpecs: () => Promise.resolve([{
|
|
267
268
|
name: MANAGE_TODO_TOOL,
|
|
268
269
|
description: TODO_TOOL_DESCRIPTION,
|
|
269
|
-
inputSchema: INPUT_SCHEMA$
|
|
270
|
+
inputSchema: INPUT_SCHEMA$6
|
|
270
271
|
}]),
|
|
271
272
|
canHandle: (name) => name === MANAGE_TODO_TOOL,
|
|
272
273
|
argCaptureTrust: (name) => name === "manage_todo" ? { tier: "reviewed" } : void 0,
|
|
@@ -499,7 +500,7 @@ const SKILL_GENERATION_SYSTEM_PROMPT = [
|
|
|
499
500
|
//#region ../agent/src/skillGeneration/toolSource.ts
|
|
500
501
|
const GENERATE_SKILL_TOOL = "generate_skill";
|
|
501
502
|
const TOOL_DESCRIPTION = "Turn the procedure just demonstrated in this conversation into a skill candidate. The user must confirm it, and a reviewer must approve it before anyone can use it.";
|
|
502
|
-
const INPUT_SCHEMA$
|
|
503
|
+
const INPUT_SCHEMA$5 = {
|
|
503
504
|
type: "object",
|
|
504
505
|
properties: {
|
|
505
506
|
name: {
|
|
@@ -557,7 +558,7 @@ const INPUT_SCHEMA$3 = {
|
|
|
557
558
|
],
|
|
558
559
|
additionalProperties: false
|
|
559
560
|
};
|
|
560
|
-
function toolError$
|
|
561
|
+
function toolError$7(code, message) {
|
|
561
562
|
return {
|
|
562
563
|
ok: false,
|
|
563
564
|
content: [],
|
|
@@ -626,7 +627,7 @@ function createSkillGenerationToolSource(options) {
|
|
|
626
627
|
const generator = new SkillGenerator(options);
|
|
627
628
|
const call = async (_name, args) => {
|
|
628
629
|
const draft = readDraft(args);
|
|
629
|
-
if (typeof draft === "string") return toolError$
|
|
630
|
+
if (typeof draft === "string") return toolError$7("SKILL_GENERATION_VALIDATION_FAILED", draft);
|
|
630
631
|
try {
|
|
631
632
|
const outcome = await generator.generate(draft);
|
|
632
633
|
if (outcome.status === "declined") return {
|
|
@@ -650,7 +651,7 @@ function createSkillGenerationToolSource(options) {
|
|
|
650
651
|
}]
|
|
651
652
|
};
|
|
652
653
|
} catch (e) {
|
|
653
|
-
return toolError$
|
|
654
|
+
return toolError$7(e instanceof Error && "code" in e ? String(e.code) : "SKILL_GENERATION_VALIDATION_FAILED", e instanceof Error ? e.message : String(e));
|
|
654
655
|
}
|
|
655
656
|
};
|
|
656
657
|
return {
|
|
@@ -659,7 +660,7 @@ function createSkillGenerationToolSource(options) {
|
|
|
659
660
|
listToolSpecs: () => Promise.resolve([{
|
|
660
661
|
name: GENERATE_SKILL_TOOL,
|
|
661
662
|
description: TOOL_DESCRIPTION,
|
|
662
|
-
inputSchema: INPUT_SCHEMA$
|
|
663
|
+
inputSchema: INPUT_SCHEMA$5
|
|
663
664
|
}]),
|
|
664
665
|
canHandle: (name) => name === GENERATE_SKILL_TOOL,
|
|
665
666
|
argCaptureTrust: (name) => name === "generate_skill" ? { tier: "reviewed" } : void 0,
|
|
@@ -769,7 +770,7 @@ const DELEGATION_SYSTEM_PROMPT = [
|
|
|
769
770
|
//#region ../agent/src/delegation/toolSource.ts
|
|
770
771
|
const DELEGATE_TASK_TOOL = "delegate_task";
|
|
771
772
|
const DELEGATE_TOOL_DESCRIPTION = "Hand one self-contained sub-task to a sub-agent and wait for its summary. One delegation at a time.";
|
|
772
|
-
const INPUT_SCHEMA$
|
|
773
|
+
const INPUT_SCHEMA$4 = {
|
|
773
774
|
type: "object",
|
|
774
775
|
properties: {
|
|
775
776
|
todoId: {
|
|
@@ -789,7 +790,7 @@ const INPUT_SCHEMA$2 = {
|
|
|
789
790
|
required: ["todoId", "task"],
|
|
790
791
|
additionalProperties: false
|
|
791
792
|
};
|
|
792
|
-
function toolError$
|
|
793
|
+
function toolError$6(code, message) {
|
|
793
794
|
return {
|
|
794
795
|
ok: false,
|
|
795
796
|
content: [],
|
|
@@ -824,12 +825,12 @@ function createDelegationToolSource(options) {
|
|
|
824
825
|
const call = async (_name, args) => {
|
|
825
826
|
const todoId = args["todoId"];
|
|
826
827
|
const task = args["task"];
|
|
827
|
-
if (typeof todoId !== "string" || todoId === "") return toolError$
|
|
828
|
-
if (typeof task !== "string" || task.trim() === "") return toolError$
|
|
828
|
+
if (typeof todoId !== "string" || todoId === "") return toolError$6("VALIDATION_FAILED", "The \"todoId\" argument is required and must be a string");
|
|
829
|
+
if (typeof task !== "string" || task.trim() === "") return toolError$6("VALIDATION_FAILED", "The \"task\" argument is required and must be a non-empty string");
|
|
829
830
|
const rawTools = args["allowedTools"];
|
|
830
831
|
let allowedTools;
|
|
831
832
|
if (rawTools !== void 0) {
|
|
832
|
-
if (!Array.isArray(rawTools) || rawTools.some((entry) => typeof entry !== "string")) return toolError$
|
|
833
|
+
if (!Array.isArray(rawTools) || rawTools.some((entry) => typeof entry !== "string")) return toolError$6("VALIDATION_FAILED", "The \"allowedTools\" argument must be an array of tool names");
|
|
833
834
|
allowedTools = rawTools;
|
|
834
835
|
}
|
|
835
836
|
try {
|
|
@@ -861,7 +862,7 @@ function createDelegationToolSource(options) {
|
|
|
861
862
|
}]
|
|
862
863
|
};
|
|
863
864
|
} catch (e) {
|
|
864
|
-
return toolError$
|
|
865
|
+
return toolError$6(e instanceof Error && "code" in e ? String(e.code) : "DELEGATION_FAILED", e instanceof Error ? e.message : String(e));
|
|
865
866
|
}
|
|
866
867
|
};
|
|
867
868
|
return {
|
|
@@ -871,7 +872,7 @@ function createDelegationToolSource(options) {
|
|
|
871
872
|
listToolSpecs: () => Promise.resolve([{
|
|
872
873
|
name: DELEGATE_TASK_TOOL,
|
|
873
874
|
description: DELEGATE_TOOL_DESCRIPTION,
|
|
874
|
-
inputSchema: INPUT_SCHEMA$
|
|
875
|
+
inputSchema: INPUT_SCHEMA$4
|
|
875
876
|
}]),
|
|
876
877
|
canHandle: (name) => name === DELEGATE_TASK_TOOL,
|
|
877
878
|
argCaptureTrust: (name) => name === "delegate_task" ? { tier: "reviewed" } : void 0,
|
|
@@ -1274,7 +1275,7 @@ const PERCEPTION_SYSTEM_PROMPT = [
|
|
|
1274
1275
|
//#endregion
|
|
1275
1276
|
//#region ../agent/src/perception/toolSource.ts
|
|
1276
1277
|
const PERCEIVE_PAGE_TOOL = "perceive_page";
|
|
1277
|
-
const DESCRIPTION$
|
|
1278
|
+
const DESCRIPTION$4 = "Read the parts of the current page the host made readable, as a structured accessibility outline. Read-only: it cannot click, type, submit or navigate.";
|
|
1278
1279
|
/**
|
|
1279
1280
|
* 两个参数,且它们都扩不出一寸新范围(AC-10.8 的判据由 0.14.0 分册 22 §3 修订)。
|
|
1280
1281
|
*
|
|
@@ -1287,7 +1288,7 @@ const DESCRIPTION$1 = "Read the parts of the current page the host made readable
|
|
|
1287
1288
|
* 「模型看到的工具契约」随宿主配置漂移,同一段对话里换个设置就变一次形状,
|
|
1288
1289
|
* 而模型的历史消息里还留着旧形状。宁可多一个恒定存在、关闭时明确拒绝的参数。
|
|
1289
1290
|
*/
|
|
1290
|
-
const INPUT_SCHEMA$
|
|
1291
|
+
const INPUT_SCHEMA$3 = {
|
|
1291
1292
|
type: "object",
|
|
1292
1293
|
properties: {
|
|
1293
1294
|
cursor: {
|
|
@@ -1308,7 +1309,7 @@ const SUBTREE_CODES = /* @__PURE__ */ new Set([
|
|
|
1308
1309
|
"PERCEPTION_SUBTREE_OUT_OF_SCOPE",
|
|
1309
1310
|
"PERCEPTION_SUBTREE_UNKNOWN_REF"
|
|
1310
1311
|
]);
|
|
1311
|
-
function toolError$
|
|
1312
|
+
function toolError$5(code, message) {
|
|
1312
1313
|
return {
|
|
1313
1314
|
ok: false,
|
|
1314
1315
|
content: [],
|
|
@@ -1400,8 +1401,8 @@ function createPagePerceptionToolSource(options) {
|
|
|
1400
1401
|
kind: "page-perception",
|
|
1401
1402
|
listToolSpecs: () => Promise.resolve(policy.enabled ? [{
|
|
1402
1403
|
name: PERCEIVE_PAGE_TOOL,
|
|
1403
|
-
description: DESCRIPTION$
|
|
1404
|
-
inputSchema: INPUT_SCHEMA$
|
|
1404
|
+
description: DESCRIPTION$4,
|
|
1405
|
+
inputSchema: INPUT_SCHEMA$3
|
|
1405
1406
|
}] : []),
|
|
1406
1407
|
systemPrompt: () => Promise.resolve(policy.enabled ? PERCEPTION_SYSTEM_PROMPT : void 0),
|
|
1407
1408
|
canHandle: (name) => name === PERCEIVE_PAGE_TOOL,
|
|
@@ -1415,11 +1416,11 @@ function createPagePerceptionToolSource(options) {
|
|
|
1415
1416
|
};
|
|
1416
1417
|
const cursor = args["cursor"];
|
|
1417
1418
|
const ref = args["ref"];
|
|
1418
|
-
if (ref !== void 0 && typeof ref !== "string") return toolError$
|
|
1419
|
+
if (ref !== void 0 && typeof ref !== "string") return toolError$5("PERCEPTION_REF_INVALID", "perceive_page needs \"ref\" to be a string when it is present.");
|
|
1419
1420
|
if (cursor !== void 0) {
|
|
1420
1421
|
const index = typeof cursor === "string" ? pending?.cursors.get(cursor) : void 0;
|
|
1421
|
-
if (!paged || pending === void 0 || index === void 0 || pending.runId !== context?.runId) return toolError$
|
|
1422
|
-
if (ref !== void 0 && ref !== pending.subtreeRef) return toolError$
|
|
1422
|
+
if (!paged || pending === void 0 || index === void 0 || pending.runId !== context?.runId) return toolError$5("PERCEPTION_CURSOR_INVALID", CURSOR_REJECTED);
|
|
1423
|
+
if (ref !== void 0 && ref !== pending.subtreeRef) return toolError$5("PERCEPTION_CURSOR_INVALID", CURSOR_REJECTED);
|
|
1423
1424
|
return resultOf(pending, index);
|
|
1424
1425
|
}
|
|
1425
1426
|
try {
|
|
@@ -1481,8 +1482,8 @@ function createPagePerceptionToolSource(options) {
|
|
|
1481
1482
|
return resultOf(pending, 0);
|
|
1482
1483
|
} catch (e) {
|
|
1483
1484
|
const code = e instanceof WebSkillError ? e.code : void 0;
|
|
1484
|
-
if (code !== void 0 && SUBTREE_CODES.has(code)) return toolError$
|
|
1485
|
-
return toolError$
|
|
1485
|
+
if (code !== void 0 && SUBTREE_CODES.has(code)) return toolError$5(code, messageOf(e));
|
|
1486
|
+
return toolError$5("PERCEPTION_FAILED", `perceive_page failed: ${messageOf(e)}`);
|
|
1486
1487
|
}
|
|
1487
1488
|
}
|
|
1488
1489
|
};
|
|
@@ -1655,7 +1656,7 @@ const RECORD_LIMIT$1 = 50;
|
|
|
1655
1656
|
function sanitizeDescription(value) {
|
|
1656
1657
|
return sanitizeUntrustedLine(value, DESCRIPTION_LIMIT);
|
|
1657
1658
|
}
|
|
1658
|
-
function isRecord(value) {
|
|
1659
|
+
function isRecord$2(value) {
|
|
1659
1660
|
return typeof value === "object" && value !== null;
|
|
1660
1661
|
}
|
|
1661
1662
|
/**
|
|
@@ -1665,7 +1666,7 @@ function isRecord(value) {
|
|
|
1665
1666
|
* 标成后者会让读代码的人以为它已经被验证过了。
|
|
1666
1667
|
*/
|
|
1667
1668
|
function validate(raw, provenance) {
|
|
1668
|
-
if (!isRecord(raw)) return void 0;
|
|
1669
|
+
if (!isRecord$2(raw)) return void 0;
|
|
1669
1670
|
const { id, kind, description, target } = raw;
|
|
1670
1671
|
if (typeof id !== "string" || !ID_PATTERN.test(id)) return void 0;
|
|
1671
1672
|
if (typeof kind !== "string" || !OFFERABLE_KINDS.includes(kind)) return void 0;
|
|
@@ -2099,19 +2100,21 @@ const PAGE_ACTION_SYSTEM_PROMPT = [
|
|
|
2099
2100
|
"A reference expires as soon as the page is perceived again; perceive first, then act.",
|
|
2100
2101
|
"After an action, the result tells you where it led. \"openedTab\" means a new page opened — switch to it with focus_page_tab to read it. \"navigated\" means this page itself moved to a new address. \"elevatedModal\" means a dialog opened on this page. If none of the three appear, the page did not change.",
|
|
2101
2102
|
"Use the \"back\" action to return to the previous page in this page's history. It takes no \"ref\" and no \"value\", and it only affects the page you are currently on. After going back, always perceive_page again: every reference from before the action is dead.",
|
|
2102
|
-
"Use the \"scroll\" action only when the page itself says there is more than you can see (for example a list that says it has 20 rows but shows 13). Pass the \"ref\" of any element inside the region you want to scroll and a \"value\" of \"down\" or \"up\"; the region is found for you, so do not look for a reference to the scrollbar or the container. The result carries \"scrolled\": when \"atEnd\" is true there is nothing further in that direction — stop scrolling and answer with what you have. Perceive the page again after each scroll: rows that left the screen no longer exist and their references are dead."
|
|
2103
|
+
"Use the \"scroll\" action only when the page itself says there is more than you can see (for example a list that says it has 20 rows but shows 13). Pass the \"ref\" of any element inside the region you want to scroll and a \"value\" of \"down\" or \"up\"; the region is found for you, so do not look for a reference to the scrollbar or the container. The result carries \"scrolled\": when \"atEnd\" is true there is nothing further in that direction — stop scrolling and answer with what you have. Perceive the page again after each scroll: rows that left the screen no longer exist and their references are dead.",
|
|
2104
|
+
"Use the \"hover\" action when a menu, tooltip or icon only appears while the pointer rests on something — a dropdown that ignores clicks, or a table header whose filter icon is invisible until hovered. Pass the \"ref\" of the thing to hover over and no \"value\". Then perceive_page again: whatever appeared is new and has no reference until you read it. If the result says \"noop\": true, nothing on the page reacted, and hovering again will not help — some pages reveal those panels through a pure CSS effect that only a real pointer can trigger. Say so and offer another route instead of retrying.",
|
|
2105
|
+
"Use the \"drag\" action to drag one element onto another — reordering a list by its drag handle, for example. Pass the \"ref\" of what you are dragging and a \"value\" holding the ref of where it should land. Both must come from a perceive_page result, so perceive first and make sure the destination is actually on screen. You can only express a start and an end; there is no path, speed or pause in between."
|
|
2103
2106
|
].join("\n");
|
|
2104
2107
|
|
|
2105
2108
|
//#endregion
|
|
2106
2109
|
//#region ../agent/src/pageAction/toolSource.ts
|
|
2107
2110
|
const PAGE_ACTION_TOOL = "act_on_page";
|
|
2108
|
-
const DESCRIPTION = "Act on one element of the current page. The target must be a \"ref\" from a fresh perceive_page result; selectors are rejected. Each call asks the user to confirm before anything happens.";
|
|
2111
|
+
const DESCRIPTION$3 = "Act on one element of the current page. The target must be a \"ref\" from a fresh perceive_page result; selectors are rejected. Each call asks the user to confirm before anything happens.";
|
|
2109
2112
|
/**
|
|
2110
2113
|
* 入参里**没有** `scope` / `selector` / `include` / `frame` 一类字段(AC-23.2):
|
|
2111
2114
|
* 工具一旦接受它们,「能操作哪里」就有了一条从模型输出流进来的路径。
|
|
2112
2115
|
* 模型能表达的只有「在哪个句柄上做哪个动作」。
|
|
2113
2116
|
*/
|
|
2114
|
-
const INPUT_SCHEMA = {
|
|
2117
|
+
const INPUT_SCHEMA$2 = {
|
|
2115
2118
|
type: "object",
|
|
2116
2119
|
properties: {
|
|
2117
2120
|
ref: {
|
|
@@ -2124,13 +2127,13 @@ const INPUT_SCHEMA = {
|
|
|
2124
2127
|
},
|
|
2125
2128
|
value: {
|
|
2126
2129
|
type: "string",
|
|
2127
|
-
description: "Text for \"fill\", the option name for \"select\", \"true\"/\"false\" for \"set\",
|
|
2130
|
+
description: "Text for \"fill\", the option name for \"select\", \"true\"/\"false\" for \"set\", \"down\"/\"up\" for \"scroll\", or the destination ref for \"drag\". Not used by \"hover\" or \"attach\"."
|
|
2128
2131
|
}
|
|
2129
2132
|
},
|
|
2130
2133
|
required: ["action"],
|
|
2131
2134
|
additionalProperties: false
|
|
2132
2135
|
};
|
|
2133
|
-
function toolError$
|
|
2136
|
+
function toolError$4(code, message) {
|
|
2134
2137
|
return {
|
|
2135
2138
|
ok: false,
|
|
2136
2139
|
content: [],
|
|
@@ -2153,8 +2156,8 @@ function createPageActionToolSource(options) {
|
|
|
2153
2156
|
kind: "page-action",
|
|
2154
2157
|
listToolSpecs: () => Promise.resolve(policy.enabled ? [{
|
|
2155
2158
|
name: PAGE_ACTION_TOOL,
|
|
2156
|
-
description: DESCRIPTION,
|
|
2157
|
-
inputSchema: INPUT_SCHEMA
|
|
2159
|
+
description: DESCRIPTION$3,
|
|
2160
|
+
inputSchema: INPUT_SCHEMA$2
|
|
2158
2161
|
}] : []),
|
|
2159
2162
|
systemPrompt: () => Promise.resolve(policy.enabled ? PAGE_ACTION_SYSTEM_PROMPT : void 0),
|
|
2160
2163
|
canHandle: (name) => name === PAGE_ACTION_TOOL,
|
|
@@ -2180,7 +2183,7 @@ function createPageActionToolSource(options) {
|
|
|
2180
2183
|
},
|
|
2181
2184
|
call: async (_name, args, context) => {
|
|
2182
2185
|
const action = args["action"];
|
|
2183
|
-
if (typeof action !== "string" || !PAGE_ACTION_KINDS.includes(action)) return toolError$
|
|
2186
|
+
if (typeof action !== "string" || !PAGE_ACTION_KINDS.includes(action)) return toolError$4("VALIDATION_FAILED", `act_on_page needs one of ${PAGE_ACTION_KINDS.join(" / ")}.`);
|
|
2184
2187
|
const runContext = { ...context?.runId !== void 0 ? { runId: context.runId } : {} };
|
|
2185
2188
|
const run = async (request) => {
|
|
2186
2189
|
try {
|
|
@@ -2197,25 +2200,25 @@ function createPageActionToolSource(options) {
|
|
|
2197
2200
|
} }
|
|
2198
2201
|
};
|
|
2199
2202
|
} catch (e) {
|
|
2200
|
-
return toolError$
|
|
2203
|
+
return toolError$4(e instanceof WebSkillError ? e.code : "PAGE_ACTION_FAILED", messageOf(e));
|
|
2201
2204
|
}
|
|
2202
2205
|
};
|
|
2203
2206
|
if (action === "back") {
|
|
2204
|
-
if ("ref" in args || "value" in args) return toolError$
|
|
2207
|
+
if ("ref" in args || "value" in args) return toolError$4("VALIDATION_FAILED", "act_on_page with \"back\" takes no \"ref\" and no \"value\".");
|
|
2205
2208
|
return await run({
|
|
2206
2209
|
ref: PAGE_DOCUMENT_REF,
|
|
2207
2210
|
action: "back"
|
|
2208
2211
|
});
|
|
2209
2212
|
}
|
|
2210
2213
|
const ref = args["ref"];
|
|
2211
|
-
if (typeof ref !== "string") return toolError$
|
|
2214
|
+
if (typeof ref !== "string") return toolError$4("VALIDATION_FAILED", `act_on_page needs a "ref" string when the action is "${action}".`);
|
|
2212
2215
|
const value = args["value"];
|
|
2213
2216
|
if ([
|
|
2214
2217
|
"fill",
|
|
2215
2218
|
"select",
|
|
2216
2219
|
"set"
|
|
2217
|
-
].includes(action) && typeof value !== "string") return toolError$
|
|
2218
|
-
if (action === "scroll" && value !== "down" && value !== "up") return toolError$
|
|
2220
|
+
].includes(action) && typeof value !== "string") return toolError$4("VALIDATION_FAILED", `act_on_page needs a "value" string when the action is "${action}".`);
|
|
2221
|
+
if (action === "scroll" && value !== "down" && value !== "up") return toolError$4("VALIDATION_FAILED", "act_on_page with \"scroll\" needs a \"value\" of \"down\" or \"up\".");
|
|
2219
2222
|
return await run({
|
|
2220
2223
|
ref,
|
|
2221
2224
|
action,
|
|
@@ -2457,7 +2460,7 @@ const SPECS = [
|
|
|
2457
2460
|
inputSchema: TAB_SCHEMA
|
|
2458
2461
|
}
|
|
2459
2462
|
];
|
|
2460
|
-
function toolError$
|
|
2463
|
+
function toolError$3(code, message) {
|
|
2461
2464
|
return {
|
|
2462
2465
|
ok: false,
|
|
2463
2466
|
content: [],
|
|
@@ -2492,7 +2495,7 @@ function createTabWorksetToolSource(options) {
|
|
|
2492
2495
|
}]
|
|
2493
2496
|
};
|
|
2494
2497
|
const handle = args["tab"];
|
|
2495
|
-
if (typeof handle !== "string" || handle === "") return toolError$
|
|
2498
|
+
if (typeof handle !== "string" || handle === "") return toolError$3("VALIDATION_FAILED", `${name} needs a "tab" handle from list_page_tabs.`);
|
|
2496
2499
|
return {
|
|
2497
2500
|
ok: true,
|
|
2498
2501
|
content: [{
|
|
@@ -2501,7 +2504,7 @@ function createTabWorksetToolSource(options) {
|
|
|
2501
2504
|
}]
|
|
2502
2505
|
};
|
|
2503
2506
|
} catch (e) {
|
|
2504
|
-
return toolError$
|
|
2507
|
+
return toolError$3(e instanceof WebSkillError ? e.code : "PAGE_TAB_FAILED", messageOf(e));
|
|
2505
2508
|
}
|
|
2506
2509
|
}
|
|
2507
2510
|
};
|
|
@@ -2517,12 +2520,12 @@ function formatSize(bytes) {
|
|
|
2517
2520
|
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
|
|
2518
2521
|
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
2519
2522
|
}
|
|
2520
|
-
/**
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
}
|
|
2523
|
+
/** `document-text` 下的三种格式各自的抽取器与称呼 */
|
|
2524
|
+
const EXTRACTED_LABEL = {
|
|
2525
|
+
docx: "Word documents",
|
|
2526
|
+
xlsx: "Excel workbooks",
|
|
2527
|
+
pptx: "PowerPoint presentations"
|
|
2528
|
+
};
|
|
2526
2529
|
/**
|
|
2527
2530
|
* 读取本机下载文件的策略(0.14.0 分册 20)。
|
|
2528
2531
|
*
|
|
@@ -2622,6 +2625,42 @@ var DownloadedFilePolicy = class {
|
|
|
2622
2625
|
});
|
|
2623
2626
|
return result;
|
|
2624
2627
|
}
|
|
2628
|
+
/**
|
|
2629
|
+
* 取原始字节,不做形态化(0.21.0 分册 22 · FR-22.4)。
|
|
2630
|
+
*
|
|
2631
|
+
* 授权卡、审计留痕与 `read()` 完全一致——逐单元读文档要的是字节本身,
|
|
2632
|
+
* 但绝不能因此绕过策略:那等于给「读用户本机磁盘」开第二个没有授权的入口。
|
|
2633
|
+
* @experimental
|
|
2634
|
+
*/
|
|
2635
|
+
async readBytes(id) {
|
|
2636
|
+
const reader = this.#reader();
|
|
2637
|
+
const entry = await this.#resolve(id);
|
|
2638
|
+
const approved = await this.#authorize("read", entry);
|
|
2639
|
+
if (approved === false) {
|
|
2640
|
+
const reason = `Reading "${entry.name}" was declined by the user.`;
|
|
2641
|
+
await this.#record({
|
|
2642
|
+
action: "read",
|
|
2643
|
+
...describe(entry),
|
|
2644
|
+
approved: false,
|
|
2645
|
+
ok: false,
|
|
2646
|
+
reason
|
|
2647
|
+
});
|
|
2648
|
+
throw new WebSkillError("TOOL_DENIED", reason);
|
|
2649
|
+
}
|
|
2650
|
+
const { contentType, bytes } = await reader.read(id);
|
|
2651
|
+
await this.#record({
|
|
2652
|
+
action: "read",
|
|
2653
|
+
...describe(entry),
|
|
2654
|
+
contentType,
|
|
2655
|
+
approved,
|
|
2656
|
+
ok: true
|
|
2657
|
+
});
|
|
2658
|
+
return {
|
|
2659
|
+
entry,
|
|
2660
|
+
contentType,
|
|
2661
|
+
bytes
|
|
2662
|
+
};
|
|
2663
|
+
}
|
|
2625
2664
|
async #materialize(entry, contentType, kind, bytes) {
|
|
2626
2665
|
if (kind === "text" || kind === "document-text") {
|
|
2627
2666
|
const body = kind === "text" ? new TextDecoder().decode(bytes) : await this.#extract(contentType, entry.name)(bytes);
|
|
@@ -2629,15 +2668,10 @@ var DownloadedFilePolicy = class {
|
|
|
2629
2668
|
entry,
|
|
2630
2669
|
contentType,
|
|
2631
2670
|
kind,
|
|
2632
|
-
text:
|
|
2633
|
-
name: entry.name,
|
|
2634
|
-
contentType,
|
|
2635
|
-
body,
|
|
2636
|
-
kind,
|
|
2637
|
-
origin: "download"
|
|
2638
|
-
})
|
|
2671
|
+
text: this.#label(entry, contentType, body, kind)
|
|
2639
2672
|
};
|
|
2640
2673
|
}
|
|
2674
|
+
if (kind === "file" && this.#overDocumentLimit(bytes)) return await this.#pdfAsText(entry, contentType, bytes);
|
|
2641
2675
|
return {
|
|
2642
2676
|
entry,
|
|
2643
2677
|
contentType,
|
|
@@ -2645,14 +2679,57 @@ var DownloadedFilePolicy = class {
|
|
|
2645
2679
|
bytes
|
|
2646
2680
|
};
|
|
2647
2681
|
}
|
|
2682
|
+
/** 与手动上传共用同一个格式化器:两条入口的产出必须逐字一致(AC-20.6) */
|
|
2683
|
+
#label(entry, contentType, body, kind) {
|
|
2684
|
+
return formatAttachmentText({
|
|
2685
|
+
name: entry.name,
|
|
2686
|
+
contentType,
|
|
2687
|
+
body,
|
|
2688
|
+
kind,
|
|
2689
|
+
origin: "download"
|
|
2690
|
+
});
|
|
2691
|
+
}
|
|
2692
|
+
/** base64 编码后才是请求体里的长度,量原始字节会漏掉那三分之一(与上传侧同口径) */
|
|
2693
|
+
#overDocumentLimit(bytes) {
|
|
2694
|
+
const max = this.#options.maxDocumentBytes?.();
|
|
2695
|
+
return max !== void 0 && Math.ceil(bytes.length / 3) * 4 > max;
|
|
2696
|
+
}
|
|
2697
|
+
/**
|
|
2698
|
+
* 超阈值 PDF 退到抽取文本(AC-21.2),抽不出就**拒绝并说明**(AC-21.3):
|
|
2699
|
+
* 半份原件是坏文件,半份抽取文本会让模型以为自己读全了。与上传侧 `#routeDocument` 同一套判断。
|
|
2700
|
+
*/
|
|
2701
|
+
async #pdfAsText(entry, contentType, bytes) {
|
|
2702
|
+
const tooLarge = `${entry.name} is ${formatSize(bytes.length)}, over the request limit for sending a document as is. It was not truncated, because a partial document would be unusable.`;
|
|
2703
|
+
const extract = this.#options.pdfExtractor;
|
|
2704
|
+
if (extract === void 0) throw new WebSkillError("TOOL_UNSUPPORTED", `${tooLarge} This environment has no PDF text extractor either, so no fallback is available. Tell the user to switch to a model that accepts large documents.`);
|
|
2705
|
+
let body;
|
|
2706
|
+
try {
|
|
2707
|
+
body = await extract(bytes);
|
|
2708
|
+
} catch (e) {
|
|
2709
|
+
throw new WebSkillError("TOOL_UNSUPPORTED", `${tooLarge} Text extraction failed as well: ${e instanceof Error ? e.message : String(e)}. Ask the user for a smaller file or a specific page range.`);
|
|
2710
|
+
}
|
|
2711
|
+
if (body.trim() === "") throw new WebSkillError("TOOL_UNSUPPORTED", `${tooLarge} It has no text layer either — most likely a scan or an image-only export — so nothing could be extracted. Tell the user this instead of guessing what it contains.`);
|
|
2712
|
+
return {
|
|
2713
|
+
entry,
|
|
2714
|
+
contentType,
|
|
2715
|
+
kind: "document-text",
|
|
2716
|
+
text: this.#label(entry, contentType, body, "document-text")
|
|
2717
|
+
};
|
|
2718
|
+
}
|
|
2648
2719
|
#extract(contentType, name) {
|
|
2649
|
-
const extract =
|
|
2720
|
+
const extract = this.#extractorFor(contentType, name);
|
|
2650
2721
|
if (extract === void 0) throw new WebSkillError("TOOL_UNSUPPORTED", this.#noExtractorReason(contentType, name));
|
|
2651
2722
|
return extract;
|
|
2652
2723
|
}
|
|
2724
|
+
#extractorFor(contentType, name) {
|
|
2725
|
+
const format = extractedDocumentFormat(contentType, name);
|
|
2726
|
+
if (format === "xlsx") return this.#options.xlsxExtractor;
|
|
2727
|
+
if (format === "pptx") return this.#options.pptxExtractor;
|
|
2728
|
+
return this.#options.docxExtractor;
|
|
2729
|
+
}
|
|
2653
2730
|
#noExtractorReason(contentType, name) {
|
|
2654
|
-
const
|
|
2655
|
-
return `${
|
|
2731
|
+
const format = extractedDocumentFormat(contentType, name);
|
|
2732
|
+
return `${EXTRACTED_LABEL[format]} cannot be read in this environment: no ${format} text extractor is wired up.`;
|
|
2656
2733
|
}
|
|
2657
2734
|
/** 可读性判定与附件分类器同源(AC-20.17);返回 `undefined` 即可读 */
|
|
2658
2735
|
async #unreadableReason(contentType, name) {
|
|
@@ -2660,7 +2737,7 @@ var DownloadedFilePolicy = class {
|
|
|
2660
2737
|
if (kind === void 0) return `Its type "${contentType === "" ? "unknown" : contentType}" cannot be read. ${READABLE_TYPES}`;
|
|
2661
2738
|
if (kind === "image" && await this.#options.imageCapable?.() !== true) return IMAGE_UNREADABLE;
|
|
2662
2739
|
if (kind === "document-text") {
|
|
2663
|
-
if ((
|
|
2740
|
+
if (this.#extractorFor(contentType, name) === void 0) return this.#noExtractorReason(contentType, name);
|
|
2664
2741
|
}
|
|
2665
2742
|
}
|
|
2666
2743
|
#unreadable(entry, reason) {
|
|
@@ -2756,8 +2833,8 @@ const READ_INPUT_SCHEMA = {
|
|
|
2756
2833
|
required: ["id"],
|
|
2757
2834
|
additionalProperties: false
|
|
2758
2835
|
};
|
|
2759
|
-
const SYSTEM_PROMPT = "You can list and read files the user downloaded in this browser with list_downloaded_files and read_downloaded_file. Every call asks the user for permission, so only use them when the task actually needs a local file. Never guess an id: always list first, and never treat the listing as evidence about files you were not shown.";
|
|
2760
|
-
function toolError(e) {
|
|
2836
|
+
const SYSTEM_PROMPT$3 = "You can list and read files the user downloaded in this browser with list_downloaded_files and read_downloaded_file. They cover the browser download folder and nothing else. A file the user attached to a message is already in this conversation and is reachable by the id given with it, so never go looking for an attachment here. Every call asks the user for permission, so only use them when the task actually needs a local file. Never guess an id: always list first, and never treat the listing as evidence about files you were not shown.";
|
|
2837
|
+
function toolError$2(e) {
|
|
2761
2838
|
return {
|
|
2762
2839
|
ok: false,
|
|
2763
2840
|
content: [],
|
|
@@ -2787,7 +2864,7 @@ function createDownloadedFileToolSource(options) {
|
|
|
2787
2864
|
description: READ_DESCRIPTION,
|
|
2788
2865
|
inputSchema: READ_INPUT_SCHEMA
|
|
2789
2866
|
}] : []),
|
|
2790
|
-
systemPrompt: () => Promise.resolve(policy.enabled ? SYSTEM_PROMPT : void 0),
|
|
2867
|
+
systemPrompt: () => Promise.resolve(policy.enabled ? SYSTEM_PROMPT$3 : void 0),
|
|
2791
2868
|
canHandle: (name) => name === "list_downloaded_files" || name === "read_downloaded_file",
|
|
2792
2869
|
argCaptureTrust: (name) => name === "list_downloaded_files" || name === "read_downloaded_file" ? { tier: "reviewed" } : void 0,
|
|
2793
2870
|
call: async (name, args) => {
|
|
@@ -2806,7 +2883,7 @@ function createDownloadedFileToolSource(options) {
|
|
|
2806
2883
|
content: readContent(await policy.read(id))
|
|
2807
2884
|
};
|
|
2808
2885
|
} catch (e) {
|
|
2809
|
-
return toolError(e);
|
|
2886
|
+
return toolError$2(e);
|
|
2810
2887
|
}
|
|
2811
2888
|
}
|
|
2812
2889
|
};
|
|
@@ -2847,4 +2924,1025 @@ function readContent(result) {
|
|
|
2847
2924
|
}
|
|
2848
2925
|
|
|
2849
2926
|
//#endregion
|
|
2850
|
-
|
|
2927
|
+
//#region ../agent/src/spreadsheet/toolSource.ts
|
|
2928
|
+
const EXPORT_SPREADSHEET_TOOL = "export_spreadsheet";
|
|
2929
|
+
const DESCRIPTION$2 = "Write tabular data — and, when they belong in the sheet, pictures — to an Excel (.xlsx) file the user can download.";
|
|
2930
|
+
const CELL_SCHEMA = {
|
|
2931
|
+
anyOf: [
|
|
2932
|
+
{ type: "string" },
|
|
2933
|
+
{ type: "number" },
|
|
2934
|
+
{ type: "boolean" },
|
|
2935
|
+
{ type: "null" }
|
|
2936
|
+
],
|
|
2937
|
+
description: "Text is never re-parsed as a number or a date: \"0123\" stays \"0123\". Use null for an empty cell."
|
|
2938
|
+
};
|
|
2939
|
+
/**
|
|
2940
|
+
* 表里的图。只在宿主接了 `resolveImage` 时才并进 schema——
|
|
2941
|
+
* 接不了字节却把字段给出去,等于邀请模型填一个填了也不算数的东西。
|
|
2942
|
+
*/
|
|
2943
|
+
const IMAGES_SCHEMA = {
|
|
2944
|
+
type: "array",
|
|
2945
|
+
maxItems: SPREADSHEET_LIMITS.imagesPerSheet,
|
|
2946
|
+
description: "Pictures placed on this sheet. Use this instead of telling the user to paste images in themselves, and instead of handing out download links for them.",
|
|
2947
|
+
items: {
|
|
2948
|
+
type: "object",
|
|
2949
|
+
properties: {
|
|
2950
|
+
ref: {
|
|
2951
|
+
type: "string",
|
|
2952
|
+
description: "Where the picture comes from: \"artifact:<file name>\" for something produced earlier in this conversation, or \"upload:<id or file name>\" for a picture the user attached. Never base64."
|
|
2953
|
+
},
|
|
2954
|
+
anchor: {
|
|
2955
|
+
type: "object",
|
|
2956
|
+
description: "Top-left cell the picture sits on. Both numbers are 1-based, so row 1 is the header row.",
|
|
2957
|
+
properties: {
|
|
2958
|
+
row: {
|
|
2959
|
+
type: "integer",
|
|
2960
|
+
minimum: 1
|
|
2961
|
+
},
|
|
2962
|
+
column: {
|
|
2963
|
+
type: "integer",
|
|
2964
|
+
minimum: 1
|
|
2965
|
+
}
|
|
2966
|
+
},
|
|
2967
|
+
required: ["row", "column"],
|
|
2968
|
+
additionalProperties: false
|
|
2969
|
+
},
|
|
2970
|
+
width: {
|
|
2971
|
+
type: "integer",
|
|
2972
|
+
minimum: 1,
|
|
2973
|
+
description: "Displayed width in pixels."
|
|
2974
|
+
},
|
|
2975
|
+
height: {
|
|
2976
|
+
type: "integer",
|
|
2977
|
+
minimum: 1,
|
|
2978
|
+
description: "Displayed height in pixels."
|
|
2979
|
+
},
|
|
2980
|
+
alt: {
|
|
2981
|
+
type: "string",
|
|
2982
|
+
description: "Short caption, used as the picture name inside the workbook."
|
|
2983
|
+
}
|
|
2984
|
+
},
|
|
2985
|
+
required: [
|
|
2986
|
+
"ref",
|
|
2987
|
+
"anchor",
|
|
2988
|
+
"width",
|
|
2989
|
+
"height"
|
|
2990
|
+
],
|
|
2991
|
+
additionalProperties: false
|
|
2992
|
+
}
|
|
2993
|
+
};
|
|
2994
|
+
function inputSchema(withImages) {
|
|
2995
|
+
return {
|
|
2996
|
+
type: "object",
|
|
2997
|
+
properties: {
|
|
2998
|
+
fileName: {
|
|
2999
|
+
type: "string",
|
|
3000
|
+
description: "Name for the workbook, for example \"sales-2026.xlsx\". A missing .xlsx suffix is added for you."
|
|
3001
|
+
},
|
|
3002
|
+
spec: {
|
|
3003
|
+
type: "object",
|
|
3004
|
+
properties: { sheets: {
|
|
3005
|
+
type: "array",
|
|
3006
|
+
minItems: 1,
|
|
3007
|
+
maxItems: SPREADSHEET_LIMITS.sheets,
|
|
3008
|
+
items: {
|
|
3009
|
+
type: "object",
|
|
3010
|
+
properties: {
|
|
3011
|
+
name: {
|
|
3012
|
+
type: "string",
|
|
3013
|
+
description: "Sheet name: at most 31 characters, and none of : \\ / ? * [ ]"
|
|
3014
|
+
},
|
|
3015
|
+
columns: {
|
|
3016
|
+
type: "array",
|
|
3017
|
+
items: { type: "string" },
|
|
3018
|
+
description: "Optional header row. When given it becomes the first, bold row."
|
|
3019
|
+
},
|
|
3020
|
+
rows: {
|
|
3021
|
+
type: "array",
|
|
3022
|
+
maxItems: SPREADSHEET_LIMITS.rowsPerSheet,
|
|
3023
|
+
items: {
|
|
3024
|
+
type: "array",
|
|
3025
|
+
maxItems: SPREADSHEET_LIMITS.columnsPerSheet,
|
|
3026
|
+
items: CELL_SCHEMA
|
|
3027
|
+
}
|
|
3028
|
+
},
|
|
3029
|
+
...withImages ? { images: IMAGES_SCHEMA } : {}
|
|
3030
|
+
},
|
|
3031
|
+
required: ["name", "rows"],
|
|
3032
|
+
additionalProperties: false
|
|
3033
|
+
}
|
|
3034
|
+
} },
|
|
3035
|
+
required: ["sheets"],
|
|
3036
|
+
additionalProperties: false
|
|
3037
|
+
}
|
|
3038
|
+
},
|
|
3039
|
+
required: ["fileName", "spec"],
|
|
3040
|
+
additionalProperties: false
|
|
3041
|
+
};
|
|
3042
|
+
}
|
|
3043
|
+
function systemPrompt$1(withImages) {
|
|
3044
|
+
return "Use export_spreadsheet when the user asks for a downloadable Excel file. The tool returns only a summary; the file itself is delivered to the user as a card. Do not re-read the file you just wrote. Cell values are written literally — the tool never turns text into a number or a date, and it writes no styling, formulas, merged cells or charts." + (withImages ? " Pictures do go in: put them in the sheet's \"images\" with a ref and an anchor cell. A picture the user attached to a message is already in this conversation — reference it as \"upload:<id or file name>\" and do not go looking for it on the page or in the download list first. When the user asks for a workbook of pictures, put the pictures in it — do not deliver the images separately and ask the user to insert them." : "");
|
|
3045
|
+
}
|
|
3046
|
+
function toolError$1(e) {
|
|
3047
|
+
return {
|
|
3048
|
+
ok: false,
|
|
3049
|
+
content: [],
|
|
3050
|
+
error: {
|
|
3051
|
+
code: e instanceof WebSkillError ? e.code : "TOOL_EXECUTION_FAILED",
|
|
3052
|
+
message: messageOf(e)
|
|
3053
|
+
}
|
|
3054
|
+
};
|
|
3055
|
+
}
|
|
3056
|
+
const isRecord$1 = (v) => typeof v === "object" && v !== null;
|
|
3057
|
+
/**
|
|
3058
|
+
* FR-10.3a 的两条分支。忘写后缀是模型的高频行为,为它打断一整轮不划算;
|
|
3059
|
+
* 写成 `.csv` 则是**意图冲突**而不是疏漏,必须拒。
|
|
3060
|
+
*
|
|
3061
|
+
* 路径安全不在这里做:`writeArtifact` 那一条(`resolveInsideRoot`)是唯一的一份,
|
|
3062
|
+
* FR-10.3b 明确禁止另写一份。
|
|
3063
|
+
*/
|
|
3064
|
+
function resolveFileName$1(value) {
|
|
3065
|
+
if (typeof value !== "string" || value.trim() === "") throw new WebSkillError("VALIDATION_FAILED", "export_spreadsheet requires a non-empty \"fileName\".");
|
|
3066
|
+
const fileName = value.trim();
|
|
3067
|
+
const lastSegment = fileName.split(/[\\/]/).pop() ?? "";
|
|
3068
|
+
const dot = lastSegment.lastIndexOf(".");
|
|
3069
|
+
const extension = dot <= 0 ? "" : lastSegment.slice(dot + 1);
|
|
3070
|
+
if (extension === "") return `${fileName}${SPREADSHEET_EXTENSION}`;
|
|
3071
|
+
if (extension.toLowerCase() === "xlsx") return fileName;
|
|
3072
|
+
throw new WebSkillError("VALIDATION_FAILED", `export_spreadsheet writes .xlsx files; "${fileName}" asks for .${extension}. Drop the suffix or use .xlsx.`);
|
|
3073
|
+
}
|
|
3074
|
+
function summarize(spec) {
|
|
3075
|
+
const sheets = spec.sheets;
|
|
3076
|
+
const imageCount = sheets.reduce((total, sheet) => total + (sheet.images?.length ?? 0), 0);
|
|
3077
|
+
return {
|
|
3078
|
+
sheetCount: sheets.length,
|
|
3079
|
+
rowCount: sheets.reduce((total, sheet) => total + sheet.rows.length, 0),
|
|
3080
|
+
...imageCount > 0 ? { imageCount } : {}
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
3083
|
+
/**
|
|
3084
|
+
* 把表格导出接到既有工具协议上。
|
|
3085
|
+
*
|
|
3086
|
+
* 做成 `ExternalToolSource` 而不是 `AgentLoop` 的内置数组(DV-4):本工具需要
|
|
3087
|
+
* `argCaptureTrust` 与 `captureArgs` 两个钩子,内置数组里没有这两个概念。
|
|
3088
|
+
* @experimental
|
|
3089
|
+
*/
|
|
3090
|
+
function createSpreadsheetToolSource(options) {
|
|
3091
|
+
const { writeArtifact, resolveImage } = options;
|
|
3092
|
+
const withImages = resolveImage !== void 0;
|
|
3093
|
+
const schema = inputSchema(withImages);
|
|
3094
|
+
const prompt = systemPrompt$1(withImages);
|
|
3095
|
+
return {
|
|
3096
|
+
kind: "spreadsheet",
|
|
3097
|
+
listToolSpecs: () => Promise.resolve(writeArtifact ? [{
|
|
3098
|
+
name: EXPORT_SPREADSHEET_TOOL,
|
|
3099
|
+
description: DESCRIPTION$2,
|
|
3100
|
+
inputSchema: schema
|
|
3101
|
+
}] : []),
|
|
3102
|
+
systemPrompt: () => Promise.resolve(writeArtifact ? prompt : void 0),
|
|
3103
|
+
canHandle: (name) => name === EXPORT_SPREADSHEET_TOOL,
|
|
3104
|
+
captureArgs: (_name, args) => {
|
|
3105
|
+
const spec = args["spec"];
|
|
3106
|
+
const sheets = isRecord$1(spec) && Array.isArray(spec["sheets"]) ? spec["sheets"] : [];
|
|
3107
|
+
const rowCount = sheets.reduce((total, sheet) => total + (isRecord$1(sheet) && Array.isArray(sheet["rows"]) ? sheet["rows"].length : 0), 0);
|
|
3108
|
+
return {
|
|
3109
|
+
...typeof args["fileName"] === "string" ? { fileName: args["fileName"] } : {},
|
|
3110
|
+
sheetCount: sheets.length,
|
|
3111
|
+
rowCount
|
|
3112
|
+
};
|
|
3113
|
+
},
|
|
3114
|
+
call: async (name, args, context) => {
|
|
3115
|
+
try {
|
|
3116
|
+
if (name !== "export_spreadsheet" || !writeArtifact) throw new WebSkillError("TOOL_UNSUPPORTED", `${name} is not available in this host.`);
|
|
3117
|
+
const fileName = resolveFileName$1(args["fileName"]);
|
|
3118
|
+
const spec = args["spec"];
|
|
3119
|
+
const bytes = await encodeSpreadsheet(spec, resolveImage ? (ref) => resolveImage(ref, context) : void 0);
|
|
3120
|
+
const artifact = await writeArtifact(fileName, bytes, SPREADSHEET_MIME_TYPE, context);
|
|
3121
|
+
return {
|
|
3122
|
+
ok: true,
|
|
3123
|
+
content: [{
|
|
3124
|
+
type: "json",
|
|
3125
|
+
data: {
|
|
3126
|
+
path: artifact.path,
|
|
3127
|
+
size: artifact.size,
|
|
3128
|
+
...summarize(spec)
|
|
3129
|
+
}
|
|
3130
|
+
}]
|
|
3131
|
+
};
|
|
3132
|
+
} catch (e) {
|
|
3133
|
+
return toolError$1(e);
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
};
|
|
3137
|
+
}
|
|
3138
|
+
|
|
3139
|
+
//#endregion
|
|
3140
|
+
//#region ../agent/src/template/toolSource.ts
|
|
3141
|
+
/** @experimental */
|
|
3142
|
+
const INSPECT_TEMPLATE_TOOL = "inspect_template";
|
|
3143
|
+
/** @experimental */
|
|
3144
|
+
const FILL_TEMPLATE_TOOL = "fill_template";
|
|
3145
|
+
const XLSX_MIME = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
3146
|
+
const DOCX_MIME = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
3147
|
+
/**
|
|
3148
|
+
* 骨架预算。模板不是文档,格子数是有限的(实测四份模板 32~56 个),
|
|
3149
|
+
* 但没人能保证用户不传一份两千行的台账进来——超了就截断并明说截了。
|
|
3150
|
+
* @experimental
|
|
3151
|
+
*/
|
|
3152
|
+
const TEMPLATE_INSPECT_BUDGET = {
|
|
3153
|
+
cells: 400,
|
|
3154
|
+
chars: 2e4
|
|
3155
|
+
};
|
|
3156
|
+
const INSPECT_DESCRIPTION = "Look inside an Excel (.xlsx) or Word (.docx) template the user attached, and report where things are: sheet names, cell addresses and their current text, merged regions; for Word, paragraph indexes and table/row/column coordinates. A cell reported with a \"formula\" is worked out by the template itself; fill the cells it reads instead. Call this before fill_template — you cannot guess a template's layout.";
|
|
3157
|
+
const FILL_DESCRIPTION = "Fill an Excel (.xlsx) or Word (.docx) template the user attached and hand the result back as a downloadable file. Everything the template already contains — styling, column widths, merged cells, formulas, headers and footers — is kept exactly as it was; only the places you name are written.";
|
|
3158
|
+
const TEMPLATE_ID = "The attachment id, or the id from list_downloaded_files.";
|
|
3159
|
+
function inspectSchema(idHint) {
|
|
3160
|
+
return {
|
|
3161
|
+
type: "object",
|
|
3162
|
+
properties: {
|
|
3163
|
+
template: {
|
|
3164
|
+
type: "string",
|
|
3165
|
+
description: templateIdDescription(idHint)
|
|
3166
|
+
},
|
|
3167
|
+
sheet: {
|
|
3168
|
+
type: "string",
|
|
3169
|
+
description: "Excel only: inspect just this sheet. Omit to inspect every sheet."
|
|
3170
|
+
}
|
|
3171
|
+
},
|
|
3172
|
+
required: ["template"],
|
|
3173
|
+
additionalProperties: false
|
|
3174
|
+
};
|
|
3175
|
+
}
|
|
3176
|
+
/**
|
|
3177
|
+
* 取件号的可用形式由**宿主接了什么**决定,所以多出来的那一句由宿主给。
|
|
3178
|
+
* 无条件写死「还可以传在线文档」的后果是:没接那条通路的宿主里,模型会反复尝试一个永远读不到的号。
|
|
3179
|
+
*/
|
|
3180
|
+
function templateIdDescription(idHint) {
|
|
3181
|
+
return idHint === void 0 || idHint === "" ? TEMPLATE_ID : `${TEMPLATE_ID} ${idHint}`;
|
|
3182
|
+
}
|
|
3183
|
+
const IMAGE_REF = {
|
|
3184
|
+
type: "string",
|
|
3185
|
+
description: "Where the picture comes from: \"artifact:<file name>\" for something produced earlier in this conversation, or \"upload:<id or file name>\" for a picture the user attached. Never base64."
|
|
3186
|
+
};
|
|
3187
|
+
function fillSchema(withImages, idHint) {
|
|
3188
|
+
return {
|
|
3189
|
+
type: "object",
|
|
3190
|
+
properties: {
|
|
3191
|
+
template: {
|
|
3192
|
+
type: "string",
|
|
3193
|
+
description: templateIdDescription(idHint)
|
|
3194
|
+
},
|
|
3195
|
+
fileName: {
|
|
3196
|
+
type: "string",
|
|
3197
|
+
description: "Name for the file you are producing. The template itself is never modified."
|
|
3198
|
+
},
|
|
3199
|
+
cells: {
|
|
3200
|
+
type: "array",
|
|
3201
|
+
description: "Excel only. Each entry writes one cell. Never write to a cell that inspect_template reported with a \"formula\" — an amount or a total is the template's own calculation, and overwriting it produces a sheet that no longer adds up when anything changes.",
|
|
3202
|
+
items: {
|
|
3203
|
+
type: "object",
|
|
3204
|
+
properties: {
|
|
3205
|
+
sheet: {
|
|
3206
|
+
type: "string",
|
|
3207
|
+
description: "Sheet name. Omit for the first sheet."
|
|
3208
|
+
},
|
|
3209
|
+
address: {
|
|
3210
|
+
type: "string",
|
|
3211
|
+
description: "A1-style address, for example \"B7\"."
|
|
3212
|
+
},
|
|
3213
|
+
value: {
|
|
3214
|
+
type: "string",
|
|
3215
|
+
description: "A plain number such as \"2\" or \"-1.5\" is stored as a number, so formulas and totals in the template add it up. Anything else is stored as text, including \"0123\", \"1,200\" and \"12 kg\" — write amounts as bare digits with no thousands separator, currency sign or unit, or the template's SUM will skip them. Everything is written literally: \"0123\" stays \"0123\". Use \"\" to clear the cell while keeping its formatting."
|
|
3216
|
+
}
|
|
3217
|
+
},
|
|
3218
|
+
required: ["address", "value"],
|
|
3219
|
+
additionalProperties: false
|
|
3220
|
+
}
|
|
3221
|
+
},
|
|
3222
|
+
rows: {
|
|
3223
|
+
type: "array",
|
|
3224
|
+
description: "Excel only. Insert blank rows before writing cells, when the template has fewer detail rows than you need. Formulas, merged regions and the rows below move down with them; addresses in \"cells\" are the positions after the insert.",
|
|
3225
|
+
items: {
|
|
3226
|
+
type: "object",
|
|
3227
|
+
properties: {
|
|
3228
|
+
sheet: { type: "string" },
|
|
3229
|
+
at: {
|
|
3230
|
+
type: "integer",
|
|
3231
|
+
minimum: 1,
|
|
3232
|
+
description: "1-based row number the new rows are inserted before."
|
|
3233
|
+
},
|
|
3234
|
+
count: {
|
|
3235
|
+
type: "integer",
|
|
3236
|
+
minimum: 1
|
|
3237
|
+
},
|
|
3238
|
+
copyStyleFrom: {
|
|
3239
|
+
type: "integer",
|
|
3240
|
+
minimum: 1,
|
|
3241
|
+
description: "Row whose formatting the new rows copy. Defaults to the row just above \"at\"."
|
|
3242
|
+
}
|
|
3243
|
+
},
|
|
3244
|
+
required: ["at", "count"],
|
|
3245
|
+
additionalProperties: false
|
|
3246
|
+
}
|
|
3247
|
+
},
|
|
3248
|
+
texts: {
|
|
3249
|
+
type: "array",
|
|
3250
|
+
description: "Word only. Each entry replaces the WHOLE text of one paragraph or one table cell — not part of it. A header line often holds several fields at once (for example \"No.: ABC-01 Serial: \"), and writing only the field you are changing deletes the others. Copy the text inspect_template reported and change just the part that differs, keeping the spacing that lines the fields up.",
|
|
3251
|
+
items: {
|
|
3252
|
+
type: "object",
|
|
3253
|
+
properties: {
|
|
3254
|
+
paragraph: {
|
|
3255
|
+
type: "integer",
|
|
3256
|
+
minimum: 0,
|
|
3257
|
+
description: "0-based body paragraph index."
|
|
3258
|
+
},
|
|
3259
|
+
table: {
|
|
3260
|
+
type: "integer",
|
|
3261
|
+
minimum: 0,
|
|
3262
|
+
description: "0-based table index."
|
|
3263
|
+
},
|
|
3264
|
+
row: {
|
|
3265
|
+
type: "integer",
|
|
3266
|
+
minimum: 0
|
|
3267
|
+
},
|
|
3268
|
+
column: {
|
|
3269
|
+
type: "integer",
|
|
3270
|
+
minimum: 0
|
|
3271
|
+
},
|
|
3272
|
+
value: {
|
|
3273
|
+
type: "string",
|
|
3274
|
+
description: "Written literally, and it becomes the entire content of the target. Use \"\" to clear the target."
|
|
3275
|
+
}
|
|
3276
|
+
},
|
|
3277
|
+
required: ["value"],
|
|
3278
|
+
additionalProperties: false
|
|
3279
|
+
}
|
|
3280
|
+
},
|
|
3281
|
+
...withImages ? { images: {
|
|
3282
|
+
type: "array",
|
|
3283
|
+
description: "Pictures to place in the template. Use this instead of telling the user to paste them in themselves. Each picture is scaled down to fit the cell it lands on and never overflows it.",
|
|
3284
|
+
items: {
|
|
3285
|
+
type: "object",
|
|
3286
|
+
properties: {
|
|
3287
|
+
ref: IMAGE_REF,
|
|
3288
|
+
sheet: {
|
|
3289
|
+
type: "string",
|
|
3290
|
+
description: "Excel only."
|
|
3291
|
+
},
|
|
3292
|
+
address: {
|
|
3293
|
+
type: "string",
|
|
3294
|
+
description: "Excel only: A1-style cell the picture sits on."
|
|
3295
|
+
},
|
|
3296
|
+
paragraph: {
|
|
3297
|
+
type: "integer",
|
|
3298
|
+
minimum: 0,
|
|
3299
|
+
description: "Word only."
|
|
3300
|
+
},
|
|
3301
|
+
table: {
|
|
3302
|
+
type: "integer",
|
|
3303
|
+
minimum: 0,
|
|
3304
|
+
description: "Word only."
|
|
3305
|
+
},
|
|
3306
|
+
row: {
|
|
3307
|
+
type: "integer",
|
|
3308
|
+
minimum: 0,
|
|
3309
|
+
description: "Word only."
|
|
3310
|
+
},
|
|
3311
|
+
column: {
|
|
3312
|
+
type: "integer",
|
|
3313
|
+
minimum: 0,
|
|
3314
|
+
description: "Word only."
|
|
3315
|
+
}
|
|
3316
|
+
},
|
|
3317
|
+
required: ["ref"],
|
|
3318
|
+
additionalProperties: false
|
|
3319
|
+
}
|
|
3320
|
+
} } : {}
|
|
3321
|
+
},
|
|
3322
|
+
required: ["template", "fileName"],
|
|
3323
|
+
additionalProperties: false
|
|
3324
|
+
};
|
|
3325
|
+
}
|
|
3326
|
+
function systemPrompt(withImages) {
|
|
3327
|
+
return "When the user gives you a form, a table or a report template and asks you to produce a filled-in copy, fill that template — do not build a new file from scratch and do not describe what to type where. Always call inspect_template first: the addresses and coordinates come from it, never from guesswork, and a wrong address is a wrong file. fill_template returns only a summary; the file is delivered to the user as a card, so do not read it back. The template is left untouched — everything it already contains is preserved, and you only name the places that change. When the template has too few detail rows for the data, insert rows instead of overwriting the rows below." + (withImages ? " A template that reserves space for a photo takes one: reference the picture as \"artifact:<name>\" or \"upload:<id or name>\" and give the cell it belongs in. Do not hand the picture over separately and ask the user to paste it." : "");
|
|
3328
|
+
}
|
|
3329
|
+
function toolError(e) {
|
|
3330
|
+
return {
|
|
3331
|
+
ok: false,
|
|
3332
|
+
content: [],
|
|
3333
|
+
error: {
|
|
3334
|
+
code: e instanceof WebSkillError ? e.code : "TOOL_EXECUTION_FAILED",
|
|
3335
|
+
message: messageOf(e)
|
|
3336
|
+
}
|
|
3337
|
+
};
|
|
3338
|
+
}
|
|
3339
|
+
const isRecord = (v) => typeof v === "object" && v !== null;
|
|
3340
|
+
/**
|
|
3341
|
+
* 照着模板出文件(0.22.0 分册 42)。
|
|
3342
|
+
*
|
|
3343
|
+
* 两个工具而不是一个:模型得先看见模板的坐标,才谈得上决定往哪儿写。
|
|
3344
|
+
* 合成一个工具就只剩两条路——要么让模型盲填,要么每次把整份骨架塞进填值的返回里。
|
|
3345
|
+
* @experimental
|
|
3346
|
+
*/
|
|
3347
|
+
function createTemplateToolSource(options) {
|
|
3348
|
+
const { files, xlsx, docx, writeArtifact, resolveImage } = options;
|
|
3349
|
+
const withImages = resolveImage !== void 0;
|
|
3350
|
+
const inspectInput = inspectSchema(options.idHint);
|
|
3351
|
+
const schema = fillSchema(withImages, options.idHint);
|
|
3352
|
+
const prompt = systemPrompt(withImages);
|
|
3353
|
+
const available = () => xlsx !== void 0 || docx !== void 0;
|
|
3354
|
+
const budgetOf = () => options.budget?.() ?? TEMPLATE_INSPECT_BUDGET;
|
|
3355
|
+
return {
|
|
3356
|
+
kind: "office-template",
|
|
3357
|
+
listToolSpecs: () => {
|
|
3358
|
+
if (!available()) return Promise.resolve([]);
|
|
3359
|
+
const specs = [{
|
|
3360
|
+
name: INSPECT_TEMPLATE_TOOL,
|
|
3361
|
+
description: INSPECT_DESCRIPTION,
|
|
3362
|
+
inputSchema: inspectInput
|
|
3363
|
+
}];
|
|
3364
|
+
if (writeArtifact !== void 0) specs.push({
|
|
3365
|
+
name: FILL_TEMPLATE_TOOL,
|
|
3366
|
+
description: FILL_DESCRIPTION,
|
|
3367
|
+
inputSchema: schema
|
|
3368
|
+
});
|
|
3369
|
+
return Promise.resolve(specs);
|
|
3370
|
+
},
|
|
3371
|
+
systemPrompt: () => Promise.resolve(available() && writeArtifact !== void 0 ? prompt : void 0),
|
|
3372
|
+
canHandle: (name) => name === "inspect_template" || name === "fill_template",
|
|
3373
|
+
argCaptureTrust: (name) => name === "inspect_template" ? { tier: "reviewed" } : void 0,
|
|
3374
|
+
captureArgs: (name, args) => {
|
|
3375
|
+
if (name === "inspect_template") return args;
|
|
3376
|
+
const count = (key) => Array.isArray(args[key]) ? args[key].length : 0;
|
|
3377
|
+
return {
|
|
3378
|
+
...typeof args["fileName"] === "string" ? { fileName: args["fileName"] } : {},
|
|
3379
|
+
filledCount: count("cells") + count("texts"),
|
|
3380
|
+
insertedRows: (Array.isArray(args["rows"]) ? args["rows"] : []).reduce((total, row) => total + (isRecord(row) && typeof row["count"] === "number" ? row["count"] : 0), 0),
|
|
3381
|
+
imageCount: count("images")
|
|
3382
|
+
};
|
|
3383
|
+
},
|
|
3384
|
+
call: async (name, args, context) => {
|
|
3385
|
+
try {
|
|
3386
|
+
if (name === "inspect_template") return await inspect(args, context);
|
|
3387
|
+
if (name === "fill_template") return await fill(args, context);
|
|
3388
|
+
throw new WebSkillError("TOOL_NOT_FOUND", `Unknown tool ${name}.`);
|
|
3389
|
+
} catch (e) {
|
|
3390
|
+
return toolError(e);
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
};
|
|
3394
|
+
async function load(raw, tool, context) {
|
|
3395
|
+
if (typeof raw !== "string" || raw === "") throw new WebSkillError("TOOL_UNSUPPORTED", `${tool} requires the "template" id of an attachment or download.`);
|
|
3396
|
+
const file = await files.read(raw, context);
|
|
3397
|
+
if (file === void 0) throw new WebSkillError("TOOL_UNSUPPORTED", `No readable file with id "${raw}". It is either not an attachment of the current message, or a download id that has since expired — list the downloads again to get a fresh one.`);
|
|
3398
|
+
const format = sniffDocumentFormat(file.bytes, file.name);
|
|
3399
|
+
if (format !== "xlsx" && format !== "docx") throw new WebSkillError("TEMPLATE_UNSUPPORTED", `"${file.name}" is not an Excel (.xlsx) or Word (.docx) file, so it cannot be used as a template.`);
|
|
3400
|
+
if ((format === "xlsx" ? xlsx : docx) === void 0) throw new WebSkillError("TEMPLATE_UNSUPPORTED", `This environment cannot fill ${format} templates.`);
|
|
3401
|
+
return {
|
|
3402
|
+
name: file.name,
|
|
3403
|
+
bytes: file.bytes,
|
|
3404
|
+
format
|
|
3405
|
+
};
|
|
3406
|
+
}
|
|
3407
|
+
async function inspect(args, context) {
|
|
3408
|
+
const template = await load(args["template"], INSPECT_TEMPLATE_TOOL, context);
|
|
3409
|
+
const budget = budgetOf();
|
|
3410
|
+
if (template.format === "xlsx") {
|
|
3411
|
+
const skeleton = await xlsx.read(template.bytes);
|
|
3412
|
+
const only = typeof args["sheet"] === "string" ? args["sheet"] : void 0;
|
|
3413
|
+
const sheets = only === void 0 ? skeleton.sheets : skeleton.sheets.filter((sheet) => sheet.name === only);
|
|
3414
|
+
if (only !== void 0 && sheets.length === 0) throw new WebSkillError("TEMPLATE_TARGET_MISSING", `The workbook has no sheet named "${only}". It has: ${skeleton.sheets.map((s) => s.name).join(", ")}.`);
|
|
3415
|
+
let left = budget.cells;
|
|
3416
|
+
let truncated = false;
|
|
3417
|
+
const described = sheets.map((sheet) => {
|
|
3418
|
+
const cells = sheet.cells.slice(0, Math.max(left, 0));
|
|
3419
|
+
if (cells.length < sheet.cells.length) truncated = true;
|
|
3420
|
+
left -= cells.length;
|
|
3421
|
+
return {
|
|
3422
|
+
name: sheet.name,
|
|
3423
|
+
range: sheet.range,
|
|
3424
|
+
cells: cells.map((cell) => ({
|
|
3425
|
+
address: cell.address,
|
|
3426
|
+
text: clip(cell.text, budget.chars),
|
|
3427
|
+
...cell.formula === void 0 ? {} : { formula: cell.formula }
|
|
3428
|
+
})),
|
|
3429
|
+
...sheet.merges.length > 0 ? { merges: [...sheet.merges] } : {},
|
|
3430
|
+
...cells.length < sheet.cells.length ? { omittedCells: sheet.cells.length - cells.length } : {}
|
|
3431
|
+
};
|
|
3432
|
+
});
|
|
3433
|
+
return json({
|
|
3434
|
+
file: template.name,
|
|
3435
|
+
format: "xlsx",
|
|
3436
|
+
sheets: described,
|
|
3437
|
+
...truncated ? { truncated: `Only the first ${budget.cells} filled cells are listed; inspect one sheet at a time.` } : {}
|
|
3438
|
+
});
|
|
3439
|
+
}
|
|
3440
|
+
const skeleton = await docx.read(template.bytes);
|
|
3441
|
+
let left = budget.cells;
|
|
3442
|
+
const paragraphs = skeleton.paragraphs.filter((paragraph) => paragraph.text !== "").slice(0, left);
|
|
3443
|
+
left -= paragraphs.length;
|
|
3444
|
+
const cells = skeleton.cells.slice(0, Math.max(left, 0));
|
|
3445
|
+
const tableCount = skeleton.cells.reduce((max, cell) => Math.max(max, cell.table + 1), 0);
|
|
3446
|
+
const omitted = skeleton.cells.length - cells.length;
|
|
3447
|
+
return json({
|
|
3448
|
+
file: template.name,
|
|
3449
|
+
format: "docx",
|
|
3450
|
+
paragraphs: paragraphs.map((paragraph) => ({
|
|
3451
|
+
index: paragraph.index,
|
|
3452
|
+
text: clip(paragraph.text, budget.chars)
|
|
3453
|
+
})),
|
|
3454
|
+
tableCount,
|
|
3455
|
+
cells: cells.map((cell) => ({
|
|
3456
|
+
table: cell.table,
|
|
3457
|
+
row: cell.row,
|
|
3458
|
+
column: cell.column,
|
|
3459
|
+
text: clip(cell.text, budget.chars),
|
|
3460
|
+
...cell.gridSpan === void 0 ? {} : { gridSpan: cell.gridSpan },
|
|
3461
|
+
...cell.vMerge === void 0 ? {} : { vMerge: cell.vMerge }
|
|
3462
|
+
})),
|
|
3463
|
+
...omitted > 0 ? { truncated: `${omitted} further table cell(s) are not listed; the template is larger than the budget.` } : {}
|
|
3464
|
+
});
|
|
3465
|
+
}
|
|
3466
|
+
async function fill(args, context) {
|
|
3467
|
+
if (writeArtifact === void 0) throw new WebSkillError("TOOL_UNSUPPORTED", `${FILL_TEMPLATE_TOOL} is not available in this host.`);
|
|
3468
|
+
const template = await load(args["template"], FILL_TEMPLATE_TOOL, context);
|
|
3469
|
+
const fileName = resolveFileName(args["fileName"], template.format);
|
|
3470
|
+
const images = await resolveImages(args["images"], context);
|
|
3471
|
+
if (template.format === "xlsx") {
|
|
3472
|
+
const spec = {
|
|
3473
|
+
...Array.isArray(args["rows"]) ? { rows: args["rows"] } : {},
|
|
3474
|
+
...Array.isArray(args["cells"]) ? { cells: args["cells"] } : {},
|
|
3475
|
+
...images.length > 0 ? { images: images.map((image) => {
|
|
3476
|
+
const address = image.placement["address"];
|
|
3477
|
+
if (typeof address !== "string" || address === "") throw new WebSkillError("TEMPLATE_ADDRESS_INVALID", `The picture "${image.ref}" needs an "address" telling which cell it goes on.`);
|
|
3478
|
+
const sheet = image.placement["sheet"];
|
|
3479
|
+
return {
|
|
3480
|
+
address,
|
|
3481
|
+
...typeof sheet === "string" ? { sheet } : {},
|
|
3482
|
+
...image.bytes
|
|
3483
|
+
};
|
|
3484
|
+
}) } : {}
|
|
3485
|
+
};
|
|
3486
|
+
const bytes = await xlsx.fill(template.bytes, spec);
|
|
3487
|
+
const artifact = await writeArtifact(fileName, bytes, XLSX_MIME, context);
|
|
3488
|
+
return json({
|
|
3489
|
+
path: artifact.path,
|
|
3490
|
+
size: artifact.size,
|
|
3491
|
+
filledCount: spec.cells?.length ?? 0,
|
|
3492
|
+
insertedRows: (spec.rows ?? []).reduce((total, row) => total + row.count, 0),
|
|
3493
|
+
...images.length > 0 ? { imageCount: images.length } : {}
|
|
3494
|
+
});
|
|
3495
|
+
}
|
|
3496
|
+
const spec = {
|
|
3497
|
+
...Array.isArray(args["texts"]) ? { texts: args["texts"] } : {},
|
|
3498
|
+
...images.length > 0 ? { images: images.map((image) => ({
|
|
3499
|
+
...pickCoordinates(image.placement),
|
|
3500
|
+
...image.bytes
|
|
3501
|
+
})) } : {}
|
|
3502
|
+
};
|
|
3503
|
+
const bytes = await docx.fill(template.bytes, spec);
|
|
3504
|
+
const artifact = await writeArtifact(fileName, bytes, DOCX_MIME, context);
|
|
3505
|
+
return json({
|
|
3506
|
+
path: artifact.path,
|
|
3507
|
+
size: artifact.size,
|
|
3508
|
+
filledCount: spec.texts?.length ?? 0,
|
|
3509
|
+
...images.length > 0 ? { imageCount: images.length } : {}
|
|
3510
|
+
});
|
|
3511
|
+
}
|
|
3512
|
+
async function resolveImages(raw, context) {
|
|
3513
|
+
if (!Array.isArray(raw) || raw.length === 0) return [];
|
|
3514
|
+
if (resolveImage === void 0) throw new WebSkillError("TEMPLATE_IMAGE_UNRESOLVED", "This host cannot resolve picture references.");
|
|
3515
|
+
const out = [];
|
|
3516
|
+
for (const entry of raw) {
|
|
3517
|
+
if (!isRecord(entry) || typeof entry["ref"] !== "string") throw new WebSkillError("TEMPLATE_IMAGE_UNRESOLVED", "Every picture needs a \"ref\".");
|
|
3518
|
+
const ref = entry["ref"];
|
|
3519
|
+
const bytes = await resolveImage(ref, context);
|
|
3520
|
+
if (bytes === void 0) throw new WebSkillError("TEMPLATE_IMAGE_UNRESOLVED", `Nothing in this conversation matches the picture reference "${ref}".`);
|
|
3521
|
+
out.push({
|
|
3522
|
+
ref,
|
|
3523
|
+
bytes,
|
|
3524
|
+
placement: entry
|
|
3525
|
+
});
|
|
3526
|
+
}
|
|
3527
|
+
return out;
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
function pickCoordinates(placement) {
|
|
3531
|
+
const out = {};
|
|
3532
|
+
for (const key of [
|
|
3533
|
+
"paragraph",
|
|
3534
|
+
"table",
|
|
3535
|
+
"row",
|
|
3536
|
+
"column"
|
|
3537
|
+
]) {
|
|
3538
|
+
const value = placement[key];
|
|
3539
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 0) out[key] = value;
|
|
3540
|
+
}
|
|
3541
|
+
return out;
|
|
3542
|
+
}
|
|
3543
|
+
/** 后缀必须与模板同格式:拿 xlsx 模板出 .docx 是意图冲突,不是笔误 */
|
|
3544
|
+
function resolveFileName(value, format) {
|
|
3545
|
+
if (typeof value !== "string" || value.trim() === "") throw new WebSkillError("VALIDATION_FAILED", `${FILL_TEMPLATE_TOOL} requires a non-empty "fileName".`);
|
|
3546
|
+
const fileName = value.trim();
|
|
3547
|
+
const lastSegment = fileName.split(/[\\/]/).pop() ?? "";
|
|
3548
|
+
const dot = lastSegment.lastIndexOf(".");
|
|
3549
|
+
const extension = dot <= 0 ? "" : lastSegment.slice(dot + 1).toLowerCase();
|
|
3550
|
+
if (extension === "") return `${fileName}.${format}`;
|
|
3551
|
+
if (extension === format) return fileName;
|
|
3552
|
+
throw new WebSkillError("VALIDATION_FAILED", `The template is a .${format} file, so the result is a .${format} file; "${fileName}" asks for .${extension}.`);
|
|
3553
|
+
}
|
|
3554
|
+
function clip(text, limit) {
|
|
3555
|
+
const max = Math.max(Math.floor(limit / 40), 40);
|
|
3556
|
+
return text.length <= max ? text : `${text.slice(0, max)}…`;
|
|
3557
|
+
}
|
|
3558
|
+
function json(data) {
|
|
3559
|
+
return {
|
|
3560
|
+
ok: true,
|
|
3561
|
+
content: [{
|
|
3562
|
+
type: "json",
|
|
3563
|
+
data
|
|
3564
|
+
}]
|
|
3565
|
+
};
|
|
3566
|
+
}
|
|
3567
|
+
|
|
3568
|
+
//#endregion
|
|
3569
|
+
//#region ../agent/src/pageImage/toolSource.ts
|
|
3570
|
+
const CAPTURE_PAGE_IMAGE_TOOL = "capture_page_image";
|
|
3571
|
+
const DESCRIPTION$1 = "Save a picture that is already on the page as a run artifact, so a skill can place it in a document.";
|
|
3572
|
+
const INPUT_SCHEMA$1 = {
|
|
3573
|
+
type: "object",
|
|
3574
|
+
properties: {
|
|
3575
|
+
ref: {
|
|
3576
|
+
type: "string",
|
|
3577
|
+
description: "Element handle from perceive_page. Only nodes marked \"capturable\" can be captured — those are the <img>, <canvas> and <svg> elements."
|
|
3578
|
+
},
|
|
3579
|
+
note: {
|
|
3580
|
+
type: "string",
|
|
3581
|
+
description: "Why you need this picture. It is shown to the user when asking for permission."
|
|
3582
|
+
}
|
|
3583
|
+
},
|
|
3584
|
+
required: ["ref"],
|
|
3585
|
+
additionalProperties: false
|
|
3586
|
+
};
|
|
3587
|
+
const SYSTEM_PROMPT$2 = "Use capture_page_image when a picture on the current page belongs in a document you are about to produce. It returns a reference, never the picture itself — pass that reference to the skill, do not try to read it back. Capturing does not tell you what the picture shows; ask for a page perception with images if you need that. Capture one picture per call, and only for nodes the page perception marked as capturable. Do not list the captured references back to the user. They are working material for the document, not a deliverable; say how many pictures went in and leave it at that.";
|
|
3588
|
+
const isFailure$1 = (result) => "reason" in result;
|
|
3589
|
+
/**
|
|
3590
|
+
* 页面取像工具(0.22.0 分册 12)。
|
|
3591
|
+
*
|
|
3592
|
+
* 与 `export_spreadsheet` 同一套 `ExternalToolSource` 范式(FR-12.1),
|
|
3593
|
+
* 不另造第二套注册机制。
|
|
3594
|
+
* @experimental
|
|
3595
|
+
*/
|
|
3596
|
+
function createPageImageToolSource(options) {
|
|
3597
|
+
const { capture } = options;
|
|
3598
|
+
return {
|
|
3599
|
+
kind: "page-image",
|
|
3600
|
+
listToolSpecs: () => Promise.resolve(capture ? [{
|
|
3601
|
+
name: CAPTURE_PAGE_IMAGE_TOOL,
|
|
3602
|
+
description: DESCRIPTION$1,
|
|
3603
|
+
inputSchema: INPUT_SCHEMA$1
|
|
3604
|
+
}] : []),
|
|
3605
|
+
systemPrompt: () => Promise.resolve(capture ? SYSTEM_PROMPT$2 : void 0),
|
|
3606
|
+
canHandle: (name) => name === CAPTURE_PAGE_IMAGE_TOOL,
|
|
3607
|
+
captureArgs: (_name, args) => typeof args["ref"] === "string" ? { ref: args["ref"] } : {},
|
|
3608
|
+
call: async (name, args, context) => {
|
|
3609
|
+
try {
|
|
3610
|
+
if (name !== "capture_page_image" || !capture) throw new WebSkillError("TOOL_UNSUPPORTED", `${name} is not available in this host.`);
|
|
3611
|
+
const ref = args["ref"];
|
|
3612
|
+
if (typeof ref !== "string" || ref.trim() === "") throw new WebSkillError("VALIDATION_FAILED", `${CAPTURE_PAGE_IMAGE_TOOL} requires a non-empty "ref" taken from a page perception.`);
|
|
3613
|
+
const note = typeof args["note"] === "string" && args["note"].trim() !== "" ? args["note"].trim() : void 0;
|
|
3614
|
+
const result = await capture({
|
|
3615
|
+
ref: ref.trim(),
|
|
3616
|
+
...note === void 0 ? {} : { note }
|
|
3617
|
+
}, context);
|
|
3618
|
+
if (isFailure$1(result)) {
|
|
3619
|
+
const levels = result.triedLevels ?? [];
|
|
3620
|
+
return {
|
|
3621
|
+
ok: false,
|
|
3622
|
+
content: [],
|
|
3623
|
+
error: {
|
|
3624
|
+
code: "TOOL_EXECUTION_FAILED",
|
|
3625
|
+
message: levels.length > 0 ? `${result.reason} (tried: ${levels.join(", ")})` : result.reason
|
|
3626
|
+
}
|
|
3627
|
+
};
|
|
3628
|
+
}
|
|
3629
|
+
return {
|
|
3630
|
+
ok: true,
|
|
3631
|
+
content: [{
|
|
3632
|
+
type: "json",
|
|
3633
|
+
data: {
|
|
3634
|
+
ref: ref.trim(),
|
|
3635
|
+
artifact: result.artifact,
|
|
3636
|
+
width: result.width,
|
|
3637
|
+
height: result.height,
|
|
3638
|
+
bytes: result.bytes
|
|
3639
|
+
}
|
|
3640
|
+
}]
|
|
3641
|
+
};
|
|
3642
|
+
} catch (e) {
|
|
3643
|
+
return {
|
|
3644
|
+
ok: false,
|
|
3645
|
+
content: [],
|
|
3646
|
+
error: {
|
|
3647
|
+
code: e instanceof WebSkillError ? e.code : "TOOL_EXECUTION_FAILED",
|
|
3648
|
+
message: messageOf(e)
|
|
3649
|
+
}
|
|
3650
|
+
};
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
};
|
|
3654
|
+
}
|
|
3655
|
+
|
|
3656
|
+
//#endregion
|
|
3657
|
+
//#region ../agent/src/remoteImage/toolSource.ts
|
|
3658
|
+
const FETCH_REMOTE_IMAGE_TOOL = "fetch_remote_image";
|
|
3659
|
+
const DESCRIPTION = "Download an image from a public URL and save it as a run artifact, so a skill can place it in a document.";
|
|
3660
|
+
const INPUT_SCHEMA = {
|
|
3661
|
+
type: "object",
|
|
3662
|
+
properties: {
|
|
3663
|
+
url: {
|
|
3664
|
+
type: "string",
|
|
3665
|
+
description: "Absolute https:// or http:// address of the image. Addresses on private networks are refused."
|
|
3666
|
+
},
|
|
3667
|
+
note: {
|
|
3668
|
+
type: "string",
|
|
3669
|
+
description: "Why you need this image. It is shown to the user when asking for permission."
|
|
3670
|
+
},
|
|
3671
|
+
describe: {
|
|
3672
|
+
type: "boolean",
|
|
3673
|
+
description: "Attach the image itself to the result so you can look at it. Set it only when the user asked what the picture shows; leave it out when you are collecting images for a document."
|
|
3674
|
+
}
|
|
3675
|
+
},
|
|
3676
|
+
required: ["url"],
|
|
3677
|
+
additionalProperties: false
|
|
3678
|
+
};
|
|
3679
|
+
const SYSTEM_PROMPT$1 = "Use fetch_remote_image when a document you are about to produce needs an image that lives at a public URL. To just show a public image in the conversation, write plain markdown —  — and do not call this tool. It returns a reference to pass on to the skill: a reference is not a path and cannot be read back. Set \"describe\" to true only when the user asked what a picture shows — it attaches the image so you can look at it, which costs extra, so leave it out while you are only collecting images for a document. Without \"describe\" you have not seen the image: say you cannot see it rather than guessing. Never infer the subject from the URL, the file name or the site it came from. The user is asked for permission before the first download, because fetching reveals their IP address to that site. Fetch one image per call, and only from addresses you got from the page or from the user — never guess a URL. Do not list the fetched references back to the user; say how many images went into the document instead.";
|
|
3680
|
+
const isFailure = (result) => "reason" in result;
|
|
3681
|
+
/**
|
|
3682
|
+
* 外链图片抓取工具(0.22.0 分册 13)。
|
|
3683
|
+
*
|
|
3684
|
+
* 与 `capture_page_image` 同一套范式:模型给出来源、宿主拿字节、回给模型的只有引用。
|
|
3685
|
+
* 三种图片来源(上传、页面、外链)因此在技能那一侧长得一模一样(AC-G1)。
|
|
3686
|
+
* @experimental
|
|
3687
|
+
*/
|
|
3688
|
+
function createRemoteImageToolSource(options) {
|
|
3689
|
+
const { fetchImage } = options;
|
|
3690
|
+
return {
|
|
3691
|
+
kind: "remote-image",
|
|
3692
|
+
listToolSpecs: () => Promise.resolve(fetchImage ? [{
|
|
3693
|
+
name: FETCH_REMOTE_IMAGE_TOOL,
|
|
3694
|
+
description: DESCRIPTION,
|
|
3695
|
+
inputSchema: INPUT_SCHEMA
|
|
3696
|
+
}] : []),
|
|
3697
|
+
systemPrompt: () => Promise.resolve(fetchImage ? SYSTEM_PROMPT$1 : void 0),
|
|
3698
|
+
canHandle: (name) => name === FETCH_REMOTE_IMAGE_TOOL,
|
|
3699
|
+
captureArgs: (_name, args) => typeof args["url"] === "string" ? { url: args["url"] } : {},
|
|
3700
|
+
call: async (name, args, context) => {
|
|
3701
|
+
try {
|
|
3702
|
+
if (name !== "fetch_remote_image" || !fetchImage) throw new WebSkillError("TOOL_UNSUPPORTED", `${name} is not available in this host.`);
|
|
3703
|
+
const url = args["url"];
|
|
3704
|
+
if (typeof url !== "string" || url.trim() === "") throw new WebSkillError("VALIDATION_FAILED", `${FETCH_REMOTE_IMAGE_TOOL} requires a non-empty "url" pointing at an image.`);
|
|
3705
|
+
const note = typeof args["note"] === "string" && args["note"].trim() !== "" ? args["note"].trim() : void 0;
|
|
3706
|
+
const describe = args["describe"] === true;
|
|
3707
|
+
const result = await fetchImage({
|
|
3708
|
+
url: url.trim(),
|
|
3709
|
+
...note === void 0 ? {} : { note },
|
|
3710
|
+
...describe ? { describe } : {}
|
|
3711
|
+
}, context);
|
|
3712
|
+
if (isFailure(result)) return {
|
|
3713
|
+
ok: false,
|
|
3714
|
+
content: [],
|
|
3715
|
+
error: {
|
|
3716
|
+
code: "TOOL_EXECUTION_FAILED",
|
|
3717
|
+
message: result.kind === void 0 ? result.reason : `${result.reason} [${result.kind}]`
|
|
3718
|
+
}
|
|
3719
|
+
};
|
|
3720
|
+
const data = describe ? result.data : void 0;
|
|
3721
|
+
const mimeType = describe ? result.mimeType : void 0;
|
|
3722
|
+
const seen = data !== void 0 && mimeType !== void 0;
|
|
3723
|
+
const content = [{
|
|
3724
|
+
type: "json",
|
|
3725
|
+
data: {
|
|
3726
|
+
url: url.trim(),
|
|
3727
|
+
artifact: result.artifact,
|
|
3728
|
+
width: result.width,
|
|
3729
|
+
height: result.height,
|
|
3730
|
+
bytes: result.bytes,
|
|
3731
|
+
...seen ? { note: "The image in the next message is the picture downloaded from this URL." } : {}
|
|
3732
|
+
}
|
|
3733
|
+
}];
|
|
3734
|
+
if (data !== void 0 && mimeType !== void 0) content.push({
|
|
3735
|
+
type: "image",
|
|
3736
|
+
id: result.artifact.replace(/^artifact:/, ""),
|
|
3737
|
+
mimeType,
|
|
3738
|
+
data
|
|
3739
|
+
});
|
|
3740
|
+
return {
|
|
3741
|
+
ok: true,
|
|
3742
|
+
content
|
|
3743
|
+
};
|
|
3744
|
+
} catch (e) {
|
|
3745
|
+
return {
|
|
3746
|
+
ok: false,
|
|
3747
|
+
content: [],
|
|
3748
|
+
error: {
|
|
3749
|
+
code: e instanceof WebSkillError ? e.code : "TOOL_EXECUTION_FAILED",
|
|
3750
|
+
message: messageOf(e)
|
|
3751
|
+
}
|
|
3752
|
+
};
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
};
|
|
3756
|
+
}
|
|
3757
|
+
|
|
3758
|
+
//#endregion
|
|
3759
|
+
//#region ../agent/src/dwg/views.ts
|
|
3760
|
+
/**
|
|
3761
|
+
* 视图取用(0.22.0 分册 23 FR-23.5 / FR-23.7)。
|
|
3762
|
+
*
|
|
3763
|
+
* 单独成文件是因为 diff、导出、投放面三边都要用同一套「哪些视图、按什么配对」,
|
|
3764
|
+
* 各写一份迟早会在某一边把空布局漏掉。
|
|
3765
|
+
*/
|
|
3766
|
+
/** 模型空间视图的固定 id。两份图纸靠它配对 */
|
|
3767
|
+
const DWG_MODEL_VIEW_ID = "Model";
|
|
3768
|
+
/**
|
|
3769
|
+
* 文档的视图列表。
|
|
3770
|
+
*
|
|
3771
|
+
* 分册 23 之前解析出的文档没有 `views`,给它补一个模型空间视图:
|
|
3772
|
+
* 那时的 `entities` 本来就是模型空间的全部内容,语义对得上。
|
|
3773
|
+
*/
|
|
3774
|
+
function dwgViewsOf(doc) {
|
|
3775
|
+
const views = doc.views;
|
|
3776
|
+
if (views && views.length > 0) return views;
|
|
3777
|
+
return [{
|
|
3778
|
+
id: DWG_MODEL_VIEW_ID,
|
|
3779
|
+
name: DWG_MODEL_VIEW_ID,
|
|
3780
|
+
kind: "model",
|
|
3781
|
+
entities: doc.entities,
|
|
3782
|
+
bounds: doc.bounds,
|
|
3783
|
+
viewports: []
|
|
3784
|
+
}];
|
|
3785
|
+
}
|
|
3786
|
+
/** 按 id 取视图;取不到就退回第一个,而不是什么都没有 */
|
|
3787
|
+
function dwgPickView(doc, viewId) {
|
|
3788
|
+
const views = dwgViewsOf(doc);
|
|
3789
|
+
return views.find((v) => v.id === viewId) ?? views[0];
|
|
3790
|
+
}
|
|
3791
|
+
|
|
3792
|
+
//#endregion
|
|
3793
|
+
//#region ../agent/src/dwg/toolSource.ts
|
|
3794
|
+
const VIEW_DWG_TOOL = "view_dwg";
|
|
3795
|
+
/** 图层汇总进上下文的条数上限:图纸动辄几十上百个图层,全给等于把预算烧在噪声上 */
|
|
3796
|
+
const MAX_LAYER_ROWS = 25;
|
|
3797
|
+
/** 叠加的硬上限(FR-35.1)。三份叠在一起没有颜色方案能让人分得清 */
|
|
3798
|
+
const MAX_DRAWINGS = 2;
|
|
3799
|
+
const VIEW_SCHEMA = {
|
|
3800
|
+
type: "object",
|
|
3801
|
+
properties: { files: {
|
|
3802
|
+
type: "array",
|
|
3803
|
+
items: {
|
|
3804
|
+
type: "string",
|
|
3805
|
+
description: "The id of a .dwg. It can come from the download list, from a file the user attached, or from the id that read_linked_document returns for a drawing on the page. The download listing marks .dwg as unreadable because it has no text to extract — pass the id here anyway."
|
|
3806
|
+
},
|
|
3807
|
+
minItems: 1,
|
|
3808
|
+
maxItems: MAX_DRAWINGS,
|
|
3809
|
+
description: "One drawing id, or two to stack them on top of each other. Two drawings are overlaid in the same coordinate system so the user can see where they differ and measure it — the first id becomes the top layer. Pass two only when the user asked to look at two drawings."
|
|
3810
|
+
} },
|
|
3811
|
+
required: ["files"],
|
|
3812
|
+
additionalProperties: false
|
|
3813
|
+
};
|
|
3814
|
+
const SYSTEM_PROMPT = "Use view_dwg to show a CAD drawing. Drawings reach you as ids, never as content: from the download list, from a file the user attached, or from read_linked_document when the drawing is linked on the page. Those three all hand you an id and nothing else — a drawing is far too large to put in front of you, so an id with no readable content is the expected outcome, not a failed read. list_downloaded_files reports .dwg as unreadable — that verdict is about extracting text, and does not apply here: hand the id to this tool and it will read the drawing itself. It opens a viewer for the user and returns only a summary to you — the drawing geometry never comes back as data, so do not ask for entity coordinates and do not try to describe the drawing from the tool result alone. The user drives the viewer: they switch between sheets and model space, turn layers on and off, and measure distances, areas and angles there. Pass two ids to stack two drawings in one viewer. That is for the user to look at, not for you to compare: you never receive the geometry, so you cannot say what changed between them. Tell the user to look and to measure, and offer to explain what the viewer controls do. The \"approximated\" count is geometry drawn imprecisely and the \"notDrawn\" count is geometry missing from the viewer entirely — say which one applies rather than merging them.";
|
|
3815
|
+
function omissionNote(doc) {
|
|
3816
|
+
return doc.omissions.map((o) => {
|
|
3817
|
+
switch (o.reason) {
|
|
3818
|
+
case "acis-not-tessellated": return `${o.count} ${o.kind} solid(s) cannot be displayed: they are ACIS solids, which this reader does not tessellate.`;
|
|
3819
|
+
case "pattern-approximated": return `${o.count} ${o.kind} entity(ies) are drawn with an approximated fill pattern, not the exact hatch definition.`;
|
|
3820
|
+
case "pattern-too-dense": return `${o.count} hatch(es) have a pattern too dense to draw; their outline is shown without the fill.`;
|
|
3821
|
+
case "linetype-unavailable": return `${o.count} entity(ies) reference a line type whose dash definition is unavailable; they are drawn solid.`;
|
|
3822
|
+
case "unsupported-entity": return `${o.count} ${o.kind} entity(ies) are not supported and were not drawn.`;
|
|
3823
|
+
}
|
|
3824
|
+
});
|
|
3825
|
+
}
|
|
3826
|
+
function summariseDocument(doc) {
|
|
3827
|
+
const views = dwgViewsOf(doc);
|
|
3828
|
+
const tally = (severity) => doc.omissions.filter((o) => o.severity === severity).reduce((total, o) => total + o.count, 0);
|
|
3829
|
+
return {
|
|
3830
|
+
version: doc.version,
|
|
3831
|
+
entities: views.reduce((total, v) => total + v.entities.length, 0),
|
|
3832
|
+
views: views.map((v) => ({
|
|
3833
|
+
id: v.id,
|
|
3834
|
+
kind: v.kind,
|
|
3835
|
+
entities: v.entities.length,
|
|
3836
|
+
viewports: v.viewports.length
|
|
3837
|
+
})),
|
|
3838
|
+
layers: doc.layers.map((l) => l.name).slice(0, MAX_LAYER_ROWS),
|
|
3839
|
+
layerCount: doc.layers.length,
|
|
3840
|
+
approximated: tally("degraded"),
|
|
3841
|
+
notDrawn: tally("dropped"),
|
|
3842
|
+
notes: omissionNote(doc)
|
|
3843
|
+
};
|
|
3844
|
+
}
|
|
3845
|
+
/**
|
|
3846
|
+
* 取件号列表(FR-35.1 / AC-35.2)。
|
|
3847
|
+
*
|
|
3848
|
+
* 超过上限**整调用拒绝**,不截断:模型传了三份却只看到两份,
|
|
3849
|
+
* 它会把缺的那份归因到图纸而不是工具。
|
|
3850
|
+
*/
|
|
3851
|
+
function requireFiles(args) {
|
|
3852
|
+
const raw = args["files"];
|
|
3853
|
+
if (!Array.isArray(raw) || raw.length === 0) throw new WebSkillError("VALIDATION_FAILED", `${VIEW_DWG_TOOL} requires a non-empty "files" array.`);
|
|
3854
|
+
if (raw.length > MAX_DRAWINGS) throw new WebSkillError("VALIDATION_FAILED", `${VIEW_DWG_TOOL} opens at most ${MAX_DRAWINGS} drawings at once; ${raw.length} were requested. Ask the user which two to look at.`);
|
|
3855
|
+
return raw.map((value, index) => {
|
|
3856
|
+
if (typeof value !== "string" || value.trim() === "") throw new WebSkillError("VALIDATION_FAILED", `${VIEW_DWG_TOOL} "files[${index}]" must be a non-empty string.`);
|
|
3857
|
+
return value.trim();
|
|
3858
|
+
});
|
|
3859
|
+
}
|
|
3860
|
+
/** 宽松判断:只要带协议头就按链接处理。宁可多脱敏一次,也不要漏掉一个带令牌的地址 */
|
|
3861
|
+
function looksLikeUrl(value) {
|
|
3862
|
+
return /^[a-z][a-z0-9+.-]*:\/\//i.test(value.trim());
|
|
3863
|
+
}
|
|
3864
|
+
/** 把 `load()` 的两种返回形态归一(FR-31.1)。判据是有没有 `document`,不是 `in` 关键字之外的猜测 */
|
|
3865
|
+
async function readDrawing(load, file, context) {
|
|
3866
|
+
const loaded = await load(file, context);
|
|
3867
|
+
if (!("document" in loaded)) return { document: loaded };
|
|
3868
|
+
const name = typeof loaded.name === "string" ? loaded.name.trim() : "";
|
|
3869
|
+
return {
|
|
3870
|
+
document: loaded.document,
|
|
3871
|
+
...name === "" ? {} : { name }
|
|
3872
|
+
};
|
|
3873
|
+
}
|
|
3874
|
+
/**
|
|
3875
|
+
* DWG 查看工具(0.22.0 分册 19,分册 34 收敛为单工具)。
|
|
3876
|
+
*
|
|
3877
|
+
* 与 `capture_page_image` 同一套 `ExternalToolSource` 范式。
|
|
3878
|
+
* 解析与投放都由宿主注入:本包不认识 `.dwg` 字节,也不认识 `documentSurface`。
|
|
3879
|
+
* @experimental
|
|
3880
|
+
*/
|
|
3881
|
+
function createDwgToolSource(options) {
|
|
3882
|
+
const { load, project } = options;
|
|
3883
|
+
const available = load !== void 0 && project !== void 0;
|
|
3884
|
+
return {
|
|
3885
|
+
kind: "dwg",
|
|
3886
|
+
listToolSpecs: () => Promise.resolve(available ? [{
|
|
3887
|
+
name: VIEW_DWG_TOOL,
|
|
3888
|
+
description: "Open a CAD drawing (DWG) in a viewer.",
|
|
3889
|
+
inputSchema: VIEW_SCHEMA
|
|
3890
|
+
}] : []),
|
|
3891
|
+
systemPrompt: () => Promise.resolve(available ? SYSTEM_PROMPT : void 0),
|
|
3892
|
+
canHandle: (name) => name === VIEW_DWG_TOOL,
|
|
3893
|
+
/**
|
|
3894
|
+
* 已审查(分册 30):文件引用**通常**是用户可见的名字。
|
|
3895
|
+
* 但 schema 允许传页面上的链接,而链接可能是带签名令牌的临时地址——
|
|
3896
|
+
* 那种东西进技能快照就是把凭证写进一份可分享的产物。
|
|
3897
|
+
* 所以不是把字段永久标成敏感,而是本次引用长得像 URL 时整调用不留。
|
|
3898
|
+
*/
|
|
3899
|
+
argCaptureTrust: (name, args) => {
|
|
3900
|
+
if (name !== "view_dwg") return void 0;
|
|
3901
|
+
const files = args["files"];
|
|
3902
|
+
return Array.isArray(files) && files.some((f) => typeof f === "string" && looksLikeUrl(f)) ? { tier: "untrusted" } : { tier: "reviewed" };
|
|
3903
|
+
},
|
|
3904
|
+
captureArgs: (_name, args) => {
|
|
3905
|
+
const files = args["files"];
|
|
3906
|
+
return Array.isArray(files) ? { files: files.filter((f) => typeof f === "string") } : {};
|
|
3907
|
+
},
|
|
3908
|
+
call: async (name, args, context) => {
|
|
3909
|
+
try {
|
|
3910
|
+
if (!available || name !== "view_dwg") throw new WebSkillError("TOOL_UNSUPPORTED", `${name} is not available in this host.`);
|
|
3911
|
+
const files = requireFiles(args);
|
|
3912
|
+
const drawings = [];
|
|
3913
|
+
for (const file of files) {
|
|
3914
|
+
const drawing = await readDrawing(load, file, context);
|
|
3915
|
+
drawings.push({
|
|
3916
|
+
title: file,
|
|
3917
|
+
document: drawing.document,
|
|
3918
|
+
...drawing.name === void 0 ? {} : { fileName: drawing.name }
|
|
3919
|
+
});
|
|
3920
|
+
}
|
|
3921
|
+
await project({ drawings }, context);
|
|
3922
|
+
return {
|
|
3923
|
+
ok: true,
|
|
3924
|
+
content: [{
|
|
3925
|
+
type: "json",
|
|
3926
|
+
data: { drawings: drawings.map((d) => ({
|
|
3927
|
+
file: d.title,
|
|
3928
|
+
...d.fileName === void 0 ? {} : { fileName: d.fileName },
|
|
3929
|
+
...summariseDocument(d.document)
|
|
3930
|
+
})) }
|
|
3931
|
+
}]
|
|
3932
|
+
};
|
|
3933
|
+
} catch (e) {
|
|
3934
|
+
return {
|
|
3935
|
+
ok: false,
|
|
3936
|
+
content: [],
|
|
3937
|
+
error: {
|
|
3938
|
+
code: e instanceof WebSkillError ? e.code : "TOOL_EXECUTION_FAILED",
|
|
3939
|
+
message: messageOf(e)
|
|
3940
|
+
}
|
|
3941
|
+
};
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
};
|
|
3945
|
+
}
|
|
3946
|
+
|
|
3947
|
+
//#endregion
|
|
3948
|
+
export { CAPTURE_PAGE_IMAGE_TOOL, CLOSE_PAGE_TAB_TOOL, DEFAULT_MAX_DATA_SOURCE_BYTES, DELEGATE_TASK_TOOL, DELEGATION_SYSTEM_PROMPT, DOCUMENT_READ_BUDGET, DOWNLOAD_WAIT_DEFAULT_MS, DOWNLOAD_WAIT_MAX_MS, DOWNLOAD_WAIT_MIN_MS, DWG_MODEL_VIEW_ID, DataSourceCandidatePolicy, DataSourcePolicy, DelegationOrchestrator, DownloadedFilePolicy, EMU_PER_PIXEL, EMU_PER_POINT, EMU_PER_TWIP, EXPORT_SPREADSHEET_TOOL, FETCH_REMOTE_IMAGE_TOOL, FILL_TEMPLATE_TOOL, FOCUS_PAGE_TAB_TOOL, GENERATE_SKILL_TOOL, INSPECT_TEMPLATE_TOOL, LIST_DOWNLOADED_FILES_TOOL, LIST_PAGE_TABS_TOOL, MANAGE_TODO_TOOL, MAX_DIGIT_WIDTH, PAGE_ACTION_KINDS, PAGE_ACTION_SYSTEM_PROMPT, PAGE_ACTION_TOOL, PAGE_DOCUMENT_REF, PDF_GARBLED_RATIO, PDF_LARGE_IMAGE_RATIO, PERCEIVE_PAGE_TOOL, PERCEPTION_PAGING_DEFAULTS, PERCEPTION_SYSTEM_PROMPT, PageActionPolicy, PagePerceptionPolicy, READ_DOCUMENT_TOOL, READ_DOWNLOADED_FILE_TOOL, SKILL_GENERATION_SYSTEM_PROMPT, SkillGenerator, TAB_WORKSET_DEFAULT_MAX, TAB_WORKSET_MAX_MAX, TAB_WORKSET_MIN_MAX, TAB_WORKSET_SYSTEM_PROMPT, TEMPLATE_INSPECT_BUDGET, TODO_SYSTEM_PROMPT, TabWorksetPolicy, TodoStore, VIEW_DWG_TOOL, asReadMode, clampDownloadWaitMs, clampWorksetMax, columnWidthToPixels, createDelegationToolSource, createDocumentToolSource, createDownloadedFileToolSource, createDwgToolSource, createHttpDataSourceTransport, createPageActionToolSource, createPageImageToolSource, createPagePerceptionToolSource, createRemoteImageToolSource, createSkillGenerationToolSource, createSpreadsheetToolSource, createTabWorksetToolSource, createTemplateToolSource, createTodoToolSource, defersImage, dwgPickView, dwgViewsOf, fitWithin, formatCellAddress, frameLabel, frameSteps, isPdfTextTrustworthy, isRangeTopLeft, measurePdfTextQuality, parseCellAddress, parseCellRange, rangeContains, readImageSize, sniffDocumentFormat, takesImage, toActionFrameScopes, toFrameScopes, withDelegationOrigin };
|