@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
|
@@ -3,11 +3,11 @@ import { b as parseSkillMarkdown, i as buildCatalog, n as SkillDiscovery, r as S
|
|
|
3
3
|
import { a as atomicWriteText, r as resolveInsideRoot, t as assertSafePathSegment } from "./pathSecurity-B1owvJAF.js";
|
|
4
4
|
import { t as assertRemoteUrlAllowed } from "./urlSafety-CiSuCJvX.js";
|
|
5
5
|
import { c as findUnpairedToolCalls, d as partsToText, f as promptText, l as interruptedToolResult, p as textParts, u as sealToolCallPairs } from "./llm-eIQNO9tr.js";
|
|
6
|
-
import { a as
|
|
6
|
+
import { a as UNSUPPORTED_DOCUMENT_MESSAGE, c as toBase64, f as SPREADSHEET_MIME_TYPE, g as DEFAULT_MAX_DOCUMENT_TEXT_BYTES, h as DEFAULT_MAX_DOCUMENT_BYTES, i as SUPPORTED_DOCUMENT_MIME, l as encodeSpreadsheet, n as READ_LINKED_DOCUMENT_TOOL, o as fromBase64, r as READ_LINKED_DOCUMENT_TOOL_NAME, s as isBytesOnlyDocument, v as TEXT_BUDGETED_CONTENT_TYPES } from "./linkedDocument-CIuh_Yp0.js";
|
|
7
7
|
import { t as mergeCatalogEntries } from "./external-_ZRQe-V9.js";
|
|
8
8
|
import { i as isArtifactCardHidden, n as extractChartSpec, r as admitArtifactMetadata, t as buildRenderResult } from "./renderResult-D9Q-Vu2x.js";
|
|
9
9
|
import { t as extractUiSpecEvents } from "./surface-DVGiCmwq.js";
|
|
10
|
-
import { t as MemoryArtifactStore } from "./memoryArtifactStore-
|
|
10
|
+
import { t as MemoryArtifactStore } from "./memoryArtifactStore-Bs7glPqm.js";
|
|
11
11
|
|
|
12
12
|
//#region ../core/src/render/xmlRenderer.ts
|
|
13
13
|
/** 转义 XML 五个特殊字符:& < > " ' */
|
|
@@ -298,12 +298,68 @@ function resolveSkillDir(skillRoot, dir, relativePath) {
|
|
|
298
298
|
return resolveInsideRoot(skillRoot, `${dir}/${relativePath}`);
|
|
299
299
|
}
|
|
300
300
|
/**
|
|
301
|
+
* 脚本上下文里 `context.writeSpreadsheet` 的图片引用解析(FR-20.8)。
|
|
302
|
+
*
|
|
303
|
+
* 前缀族与文档那一侧同源(分册 11):`artifact:` 读本 run 已写出的产物,
|
|
304
|
+
* `upload:` 读发起本 run 的那条消息里的附件。两条都**不经模型**、也不经沙箱桥——
|
|
305
|
+
* 脚本交上来的始终只是一个引用,字节在这里才第一次出现。
|
|
306
|
+
*
|
|
307
|
+
* `remote:` 不收:表格必须把字节嵌进去,留外链等于交一份在别人机器上打不开的表。
|
|
308
|
+
* 要用远端图,先落成产物再引用。
|
|
309
|
+
*/
|
|
310
|
+
function createScriptImageResolver(deps) {
|
|
311
|
+
return async (ref) => {
|
|
312
|
+
const separator = ref.indexOf(":");
|
|
313
|
+
const kind = separator === -1 ? "" : ref.slice(0, separator);
|
|
314
|
+
const rest = ref.slice(separator + 1);
|
|
315
|
+
if (kind === "artifact") {
|
|
316
|
+
if (deps.artifactStore.readArtifact === void 0) return void 0;
|
|
317
|
+
const found = (await deps.artifactStore.listArtifacts(deps.runId)).find((a) => a.path === rest);
|
|
318
|
+
if (found === void 0) return void 0;
|
|
319
|
+
return {
|
|
320
|
+
bytes: await deps.artifactStore.readArtifact(deps.runId, rest),
|
|
321
|
+
contentType: found.mimeType ?? "application/octet-stream"
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
if (kind === "upload") {
|
|
325
|
+
if (deps.uploadFiles === void 0) return void 0;
|
|
326
|
+
const files = await deps.uploadFiles.list();
|
|
327
|
+
const info = files.find((file) => file.id === rest) ?? files.find((file) => file.name === rest);
|
|
328
|
+
if (info === void 0) return void 0;
|
|
329
|
+
return {
|
|
330
|
+
bytes: await deps.uploadFiles.read(info.id),
|
|
331
|
+
contentType: info.contentType
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
if (kind === "remote") throw new WebSkillError("VALIDATION_FAILED", `image reference "${ref}" cannot be embedded: spreadsheets need the bytes, not a link. Fetch it into an artifact first and reference that.`);
|
|
335
|
+
throw new WebSkillError("VALIDATION_FAILED", `image reference "${ref}" has no known prefix; expected artifact: or upload:`);
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
301
339
|
* 脚本执行上下文:只暴露 readReference / readAsset / readAssetBinary / writeArtifact
|
|
302
340
|
* 四个显式能力,不暴露 fs 本体(沙箱语义,对齐 deferred-items D1)。
|
|
303
341
|
*/
|
|
304
342
|
function createScriptContext(deps) {
|
|
305
343
|
const { fs, artifactStore, skillName, skillRoot, runId, confirm, fetchData, uploadFiles, onWarning, onArtifactCreated } = deps;
|
|
306
344
|
const readFs = fs.withRoot?.(skillRoot) ?? fs;
|
|
345
|
+
const writeArtifact = async (path, content, options) => {
|
|
346
|
+
const metadata = options?.metadata === void 0 ? void 0 : admitArtifactMetadata(options.metadata);
|
|
347
|
+
const artifact = typeof content === "string" ? await artifactStore.createTextArtifact({
|
|
348
|
+
runId,
|
|
349
|
+
path,
|
|
350
|
+
content,
|
|
351
|
+
mimeType: options?.mimeType,
|
|
352
|
+
...metadata === void 0 ? {} : { metadata }
|
|
353
|
+
}) : await artifactStore.createBinaryArtifact({
|
|
354
|
+
runId,
|
|
355
|
+
path,
|
|
356
|
+
content,
|
|
357
|
+
mimeType: options?.mimeType,
|
|
358
|
+
...metadata === void 0 ? {} : { metadata }
|
|
359
|
+
});
|
|
360
|
+
onArtifactCreated?.(artifact);
|
|
361
|
+
return artifact;
|
|
362
|
+
};
|
|
307
363
|
return {
|
|
308
364
|
skillName,
|
|
309
365
|
runId,
|
|
@@ -316,24 +372,18 @@ function createScriptContext(deps) {
|
|
|
316
372
|
async readAssetBinary(relativePath) {
|
|
317
373
|
return readFs.readBinary(resolveSkillDir(skillRoot, "assets", relativePath));
|
|
318
374
|
},
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const
|
|
375
|
+
writeArtifact,
|
|
376
|
+
...deps.writeSpreadsheet ? { writeSpreadsheet: async (path, spec, options) => {
|
|
377
|
+
const resolveImage = createScriptImageResolver({
|
|
378
|
+
artifactStore,
|
|
322
379
|
runId,
|
|
323
|
-
|
|
324
|
-
content,
|
|
325
|
-
mimeType: options?.mimeType,
|
|
326
|
-
...metadata === void 0 ? {} : { metadata }
|
|
327
|
-
}) : await artifactStore.createBinaryArtifact({
|
|
328
|
-
runId,
|
|
329
|
-
path,
|
|
330
|
-
content,
|
|
331
|
-
mimeType: options?.mimeType,
|
|
332
|
-
...metadata === void 0 ? {} : { metadata }
|
|
380
|
+
...uploadFiles ? { uploadFiles } : {}
|
|
333
381
|
});
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
382
|
+
return writeArtifact(path, await encodeSpreadsheet(spec, resolveImage), {
|
|
383
|
+
mimeType: SPREADSHEET_MIME_TYPE,
|
|
384
|
+
...options?.metadata === void 0 ? {} : { metadata: options.metadata }
|
|
385
|
+
});
|
|
386
|
+
} } : {},
|
|
337
387
|
...confirm ? { confirm } : {},
|
|
338
388
|
...fetchData ? { fetchData } : {},
|
|
339
389
|
...deps.documentSurface ? { documentSurface: true } : {},
|
|
@@ -687,6 +737,90 @@ function renderCurrentTimePrompt(nowIso) {
|
|
|
687
737
|
*/
|
|
688
738
|
const ON_DEMAND_TOOLS_HINT = "Some tools are disclosed on demand and are not listed above. If a capability you need seems missing, look for a related skill in the catalog and read its SKILL.md first — activating it may reveal additional tools. Do not conclude the capability does not exist.";
|
|
689
739
|
|
|
740
|
+
//#endregion
|
|
741
|
+
//#region ../runtime/src/engine/visionBatch.ts
|
|
742
|
+
/**
|
|
743
|
+
* 视觉委派的分批并发调度(0.21.0 分册 26 · FR-26.5 ~ FR-26.7)。
|
|
744
|
+
*
|
|
745
|
+
* 从 `agentLoop` 里拆出来,是因为它有三条彼此独立的分支——并发、慢降级、失败隔离——
|
|
746
|
+
* 塞进那个已经三千行的文件里就只能靠端到端测,而这三条恰恰是最需要单独逼出来的。
|
|
747
|
+
* @experimental
|
|
748
|
+
*/
|
|
749
|
+
/** 并发度上限。再高只会让每一张都变慢,同时把端点的速率限制撞穿(FR-26.5) @experimental */
|
|
750
|
+
const VISION_MAX_CONCURRENCY = 3;
|
|
751
|
+
/** @experimental */
|
|
752
|
+
const VISION_DEFAULT_CONCURRENCY = 2;
|
|
753
|
+
/**
|
|
754
|
+
* 超过它就退回串行(FR-26.6)。云端视觉端点单张 3–15 秒,本地 Qwen-VL 一类 20–40 秒;
|
|
755
|
+
* 越过 25 秒说明端点已经在排队,继续并发只会让第一张有用的描述来得更晚。
|
|
756
|
+
* @experimental
|
|
757
|
+
*/
|
|
758
|
+
const VISION_SLOW_THRESHOLD_MS = 25e3;
|
|
759
|
+
/** 配错了应该退化成能用,不是让 run 失败(FR-26.5) @experimental */
|
|
760
|
+
function clampVisionConcurrency(value) {
|
|
761
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return 2;
|
|
762
|
+
return Math.min(3, Math.max(1, Math.round(value)));
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* 逐张委派、按并发度并行。
|
|
766
|
+
*
|
|
767
|
+
* 第一波结算后测一次墙钟决定要不要降级:测第一波而不是第一张,
|
|
768
|
+
* 因为并发下第一张返回得早纯属运气,一波的完成时间才反映端点的实际吞吐。
|
|
769
|
+
* 降级是**单向**的,本次委派内不再回升。
|
|
770
|
+
* @experimental
|
|
771
|
+
*/
|
|
772
|
+
async function runVisionBatch(options) {
|
|
773
|
+
const total = options.images.length;
|
|
774
|
+
const now = options.now ?? (() => Date.now());
|
|
775
|
+
const slowThresholdMs = options.slowThresholdMs ?? 25e3;
|
|
776
|
+
const outcome = {
|
|
777
|
+
described: new Array(total).fill(void 0),
|
|
778
|
+
failures: [],
|
|
779
|
+
declined: false
|
|
780
|
+
};
|
|
781
|
+
let cursor = 0;
|
|
782
|
+
let done = 0;
|
|
783
|
+
let concurrency = clampVisionConcurrency(options.concurrency);
|
|
784
|
+
const settle = (index, result) => {
|
|
785
|
+
if (result.outcome === "described") {
|
|
786
|
+
outcome.described[index] = result.text;
|
|
787
|
+
outcome.endpoint ??= result.endpoint;
|
|
788
|
+
} else if (result.outcome === "declined") outcome.declined = true;
|
|
789
|
+
else outcome.failures.push({
|
|
790
|
+
index,
|
|
791
|
+
reason: result.reason
|
|
792
|
+
});
|
|
793
|
+
done += 1;
|
|
794
|
+
options.onProgress?.(done, total);
|
|
795
|
+
};
|
|
796
|
+
const stopped = () => outcome.declined || options.signal?.aborted === true;
|
|
797
|
+
const one = async (index) => {
|
|
798
|
+
try {
|
|
799
|
+
settle(index, await options.describe(options.images[index], index));
|
|
800
|
+
} catch (e) {
|
|
801
|
+
settle(index, {
|
|
802
|
+
outcome: "failed",
|
|
803
|
+
reason: e instanceof Error ? e.message : String(e)
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
const firstWave = [];
|
|
808
|
+
const startedAt = now();
|
|
809
|
+
for (; cursor < total && firstWave.length < concurrency; cursor += 1) firstWave.push(one(cursor));
|
|
810
|
+
await Promise.all(firstWave);
|
|
811
|
+
if (now() - startedAt > slowThresholdMs) concurrency = 1;
|
|
812
|
+
const worker = async () => {
|
|
813
|
+
while (!stopped()) {
|
|
814
|
+
const index = cursor;
|
|
815
|
+
if (index >= total) return;
|
|
816
|
+
cursor += 1;
|
|
817
|
+
await one(index);
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, total - cursor) }, () => worker()));
|
|
821
|
+
return outcome;
|
|
822
|
+
}
|
|
823
|
+
|
|
690
824
|
//#endregion
|
|
691
825
|
//#region ../runtime/src/trace/traceRecorder.ts
|
|
692
826
|
/** 结构化 trace 记录器;时钟与 id 工厂可注入(golden trace 用固定值保证确定性) */
|
|
@@ -1047,6 +1181,11 @@ const LLM_TURN_CODES = /* @__PURE__ */ new Set([
|
|
|
1047
1181
|
"TOOL_SCHEMA_UNAVAILABLE"
|
|
1048
1182
|
]);
|
|
1049
1183
|
/**
|
|
1184
|
+
* 端点 200 但整轮 run 没给出任何答复时的说明。
|
|
1185
|
+
* 上下文撑爆只是最常见的成因,不是判据——判据是「一句回答都没有」这个事实本身。
|
|
1186
|
+
*/
|
|
1187
|
+
const EMPTY_RESPONSE_MESSAGE = "The model produced no answer and requested no tools, so this run has nothing to show. The most common cause is a conversation that has outgrown the model context window: start a new chat, remove large attachments, or switch to a model with a larger context window.";
|
|
1188
|
+
/**
|
|
1050
1189
|
* 模型侧图片通道收得下的位图格式(三家 provider 的交集)。
|
|
1051
1190
|
* SVG 是文本、`application/octet-stream` 是未知字节——两者编成 data URL 发出去,
|
|
1052
1191
|
* 端点当场 400(LM Studio:`'url' field must be a base64 encoded image.`),
|
|
@@ -1198,7 +1337,8 @@ var AgentLoop = class {
|
|
|
1198
1337
|
maxUnknownToolRetries: config.maxUnknownToolRetries ?? 2,
|
|
1199
1338
|
askUserMaxFields: config.askUserMaxFields ?? 20,
|
|
1200
1339
|
maxDocumentBytes: config.maxDocumentBytes ?? 256e5,
|
|
1201
|
-
maxDocumentTextBytes: config.maxDocumentTextBytes ??
|
|
1340
|
+
maxDocumentTextBytes: config.maxDocumentTextBytes ?? 512e4,
|
|
1341
|
+
visionConcurrency: clampVisionConcurrency(config.visionConcurrency),
|
|
1202
1342
|
temperature: config.temperature,
|
|
1203
1343
|
renderResult: config.renderResult,
|
|
1204
1344
|
remoteUrl: config.remoteUrl
|
|
@@ -1256,6 +1396,7 @@ var AgentLoop = class {
|
|
|
1256
1396
|
interactionSeq: 0,
|
|
1257
1397
|
surfaceActionSeq: 0,
|
|
1258
1398
|
documentSeq: 0,
|
|
1399
|
+
binaries: /* @__PURE__ */ new Map(),
|
|
1259
1400
|
messages: [],
|
|
1260
1401
|
turn: 0,
|
|
1261
1402
|
renderBlocks: [],
|
|
@@ -1421,6 +1562,7 @@ var AgentLoop = class {
|
|
|
1421
1562
|
const { llm } = this.#deps;
|
|
1422
1563
|
const { trace, messages } = state;
|
|
1423
1564
|
this.#armDeadline(state);
|
|
1565
|
+
let producedOutput = false;
|
|
1424
1566
|
try {
|
|
1425
1567
|
for (let turn = startTurn;; turn++) {
|
|
1426
1568
|
state.turn = turn;
|
|
@@ -1502,12 +1644,14 @@ var AgentLoop = class {
|
|
|
1502
1644
|
outputTokens: response.usage.outputTokens
|
|
1503
1645
|
} });
|
|
1504
1646
|
if (!response.toolCalls?.length) {
|
|
1647
|
+
if (responseText === "" && !producedOutput) return finish("failed", "llm-error", EMPTY_RESPONSE_MESSAGE, "LLM_REQUEST_FAILED");
|
|
1505
1648
|
messages.push({
|
|
1506
1649
|
role: "assistant",
|
|
1507
1650
|
content: response.content ?? []
|
|
1508
1651
|
});
|
|
1509
1652
|
return finish("completed", "final-answer", responseText);
|
|
1510
1653
|
}
|
|
1654
|
+
producedOutput = true;
|
|
1511
1655
|
await this.#lifecycle({
|
|
1512
1656
|
phase: "execute",
|
|
1513
1657
|
data: {
|
|
@@ -1698,6 +1842,7 @@ var AgentLoop = class {
|
|
|
1698
1842
|
trace,
|
|
1699
1843
|
reader: new SkillReader(this.#deps.fs, this.#deps.skillIndex),
|
|
1700
1844
|
documentSeq: 0,
|
|
1845
|
+
binaries: /* @__PURE__ */ new Map(),
|
|
1701
1846
|
activated: new Set(snapshot.activeSkillNames),
|
|
1702
1847
|
activatedTools: new Map(snapshot.activatedTools.map((d) => [d.name, d])),
|
|
1703
1848
|
skillAllowedTools: new Map(Object.entries(snapshot.skillAllowedTools ?? {})),
|
|
@@ -2126,7 +2271,11 @@ var AgentLoop = class {
|
|
|
2126
2271
|
const source = resolution.kind === "script" ? void 0 : (this.#deps.externalTools ?? []).find((s) => s.canHandle(call.name));
|
|
2127
2272
|
if (source === void 0 && !this.#checkToolAccess(state, call.name)) result = this.#deniedToolError(state, call.name);
|
|
2128
2273
|
else if (resolution.kind === "script") result = await this.#handleScriptTool(call, resolution.skillName, resolution.scriptName, state);
|
|
2129
|
-
else if (source) result = await source.call(call.name, call.arguments, {
|
|
2274
|
+
else if (source) result = await source.call(call.name, call.arguments, {
|
|
2275
|
+
runId: state.runId,
|
|
2276
|
+
signal: state.controller.signal,
|
|
2277
|
+
binaries: { get: (id) => state.binaries.get(id) }
|
|
2278
|
+
});
|
|
2130
2279
|
else {
|
|
2131
2280
|
if (resolution.kind === "not-found") state.unknownToolCalls += 1;
|
|
2132
2281
|
result = toolError(resolution.code, resolution.message, resolution.kind === "not-found" ? resolution.data : void 0);
|
|
@@ -2520,20 +2669,55 @@ var AgentLoop = class {
|
|
|
2520
2669
|
};
|
|
2521
2670
|
}
|
|
2522
2671
|
let text;
|
|
2523
|
-
if (mime
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2672
|
+
if (isBytesOnlyDocument(mime, target.href)) {
|
|
2673
|
+
state.binaries.set(id, {
|
|
2674
|
+
name,
|
|
2675
|
+
mimeType: mime,
|
|
2676
|
+
bytes: fetched.bytes
|
|
2677
|
+
});
|
|
2678
|
+
await audit(record);
|
|
2679
|
+
return {
|
|
2680
|
+
ok: true,
|
|
2681
|
+
content: [{
|
|
2682
|
+
type: "json",
|
|
2683
|
+
data: {
|
|
2684
|
+
id,
|
|
2685
|
+
name,
|
|
2686
|
+
mimeType: mime,
|
|
2687
|
+
bytes: fetched.bytes.length,
|
|
2688
|
+
note: "Stored for tools. Its contents are not readable as text — pass this id to a tool that reads this format."
|
|
2689
|
+
}
|
|
2690
|
+
}]
|
|
2691
|
+
};
|
|
2692
|
+
}
|
|
2693
|
+
const route = {
|
|
2694
|
+
[SUPPORTED_DOCUMENT_MIME.docx]: {
|
|
2695
|
+
kind: "docx",
|
|
2696
|
+
label: "Word documents",
|
|
2697
|
+
...this.#deps.docxExtractor ? { extractor: this.#deps.docxExtractor } : {}
|
|
2698
|
+
},
|
|
2699
|
+
[SUPPORTED_DOCUMENT_MIME.xlsx]: {
|
|
2700
|
+
kind: "xlsx",
|
|
2701
|
+
label: "Excel workbooks",
|
|
2702
|
+
...this.#deps.xlsxExtractor ? { extractor: this.#deps.xlsxExtractor } : {}
|
|
2703
|
+
},
|
|
2704
|
+
[SUPPORTED_DOCUMENT_MIME.pptx]: {
|
|
2705
|
+
kind: "pptx",
|
|
2706
|
+
label: "PowerPoint presentations",
|
|
2707
|
+
...this.#deps.pptxExtractor ? { extractor: this.#deps.pptxExtractor } : {}
|
|
2708
|
+
}
|
|
2709
|
+
}[mime];
|
|
2710
|
+
if (route !== void 0) {
|
|
2711
|
+
if (route.extractor === void 0) {
|
|
2528
2712
|
await audit({
|
|
2529
2713
|
...record,
|
|
2530
2714
|
ok: false,
|
|
2531
|
-
reason: `no ${kind} extractor`
|
|
2715
|
+
reason: `no ${route.kind} extractor`
|
|
2532
2716
|
});
|
|
2533
|
-
return toolError("TOOL_UNSUPPORTED", `${
|
|
2717
|
+
return toolError("TOOL_UNSUPPORTED", `${route.label} cannot be read in this environment: no ${route.kind} text extractor is configured.`);
|
|
2534
2718
|
}
|
|
2535
2719
|
try {
|
|
2536
|
-
text = await extractor(fetched.bytes);
|
|
2720
|
+
text = await route.extractor(fetched.bytes);
|
|
2537
2721
|
} catch (e) {
|
|
2538
2722
|
const message = messageOf(e);
|
|
2539
2723
|
await audit({
|
|
@@ -2811,6 +2995,131 @@ var AgentLoop = class {
|
|
|
2811
2995
|
});
|
|
2812
2996
|
return dropped > 0;
|
|
2813
2997
|
}
|
|
2998
|
+
/**
|
|
2999
|
+
* 工具产出的图 → 第二个端点认出来的文本(0.21.0 分册 17)。
|
|
3000
|
+
*
|
|
3001
|
+
* 返回 `undefined` 表示「本次不委派」,调用方原样投图——注入位缺席、
|
|
3002
|
+
* 选中模型本来就看得了图、或者这批结果里根本没有图,都走这条。
|
|
3003
|
+
* 返回值里的 `kept` 是去掉图之后仍要投出的分片(PDF 等文件分片不受影响)。
|
|
3004
|
+
*
|
|
3005
|
+
* 四种失败一律**返回可读说明**而不是抛错:静默丢图会让主模型以为自己看过了
|
|
3006
|
+
* (FR-17.5a,与 `#dropRejectedImages` 同口径)。
|
|
3007
|
+
*/
|
|
3008
|
+
async #delegateVision(state, call, carried) {
|
|
3009
|
+
const delegate = this.#deps.visionDelegate;
|
|
3010
|
+
if (delegate === void 0) return void 0;
|
|
3011
|
+
const images = carried.filter(isImagePart);
|
|
3012
|
+
if (images.length === 0) return void 0;
|
|
3013
|
+
try {
|
|
3014
|
+
if (await delegate.applies() !== true) return void 0;
|
|
3015
|
+
} catch (e) {
|
|
3016
|
+
state.trace.record("run.warning", { message: `Vision delegation availability check failed: ${messageOf(e)}` });
|
|
3017
|
+
return;
|
|
3018
|
+
}
|
|
3019
|
+
const kept = carried.filter((part) => !isImagePart(part));
|
|
3020
|
+
const subject = `the ${images.length === 1 ? "image" : `${images.length} images`} from "${call.name}"`;
|
|
3021
|
+
const dropped = `The ${images.length === 1 ? "image was" : "images were"} not attached; work from the surrounding text, and say so if the answer depends on what ${images.length === 1 ? "it shows" : "they show"}.`;
|
|
3022
|
+
const finish = (note) => {
|
|
3023
|
+
state.trace.record("run.warning", {
|
|
3024
|
+
message: note,
|
|
3025
|
+
data: {
|
|
3026
|
+
images: images.length,
|
|
3027
|
+
tool: call.name
|
|
3028
|
+
}
|
|
3029
|
+
});
|
|
3030
|
+
return {
|
|
3031
|
+
kept,
|
|
3032
|
+
note
|
|
3033
|
+
};
|
|
3034
|
+
};
|
|
3035
|
+
const remainingTurns = state.maxTurns - state.turn;
|
|
3036
|
+
const remainingMs = state.totalTimeoutMs - this.#elapsed(state);
|
|
3037
|
+
const timeoutMs = Math.min(state.toolTimeoutMs * images.length, remainingMs);
|
|
3038
|
+
if (remainingTurns < 1 || timeoutMs <= 0) return finish(`The parent run has no budget left to describe ${subject} (${remainingTurns} turns and ${Math.max(0, Math.round(remainingMs))} ms remaining). ${dropped}`);
|
|
3039
|
+
const payload = images.map((part) => ({
|
|
3040
|
+
mimeType: part.mimeType,
|
|
3041
|
+
data: part.data
|
|
3042
|
+
}));
|
|
3043
|
+
const cancelled = state.controller.signal;
|
|
3044
|
+
const describeOne = async (image) => {
|
|
3045
|
+
const left = state.totalTimeoutMs - this.#elapsed(state);
|
|
3046
|
+
const budget = Math.min(state.toolTimeoutMs, left);
|
|
3047
|
+
if (budget <= 0) return {
|
|
3048
|
+
outcome: "failed",
|
|
3049
|
+
reason: "the parent run has no time budget left"
|
|
3050
|
+
};
|
|
3051
|
+
const controller = new AbortController();
|
|
3052
|
+
const cancelParent = () => controller.abort(cancelled.reason);
|
|
3053
|
+
cancelled.addEventListener("abort", cancelParent, { once: true });
|
|
3054
|
+
let timedOut = false;
|
|
3055
|
+
const timer = setTimeout(() => {
|
|
3056
|
+
timedOut = true;
|
|
3057
|
+
controller.abort(/* @__PURE__ */ new Error("vision delegation timed out"));
|
|
3058
|
+
}, budget);
|
|
3059
|
+
try {
|
|
3060
|
+
const one = await delegate.describe({
|
|
3061
|
+
images: [image],
|
|
3062
|
+
task: state.run.userPrompt,
|
|
3063
|
+
source: call.name,
|
|
3064
|
+
batch: {
|
|
3065
|
+
id: call.id,
|
|
3066
|
+
total: payload.length
|
|
3067
|
+
},
|
|
3068
|
+
timeoutMs: budget,
|
|
3069
|
+
signal: controller.signal
|
|
3070
|
+
});
|
|
3071
|
+
return timedOut ? {
|
|
3072
|
+
outcome: "failed",
|
|
3073
|
+
reason: `exceeded its ${budget} ms budget`
|
|
3074
|
+
} : one;
|
|
3075
|
+
} catch (e) {
|
|
3076
|
+
return {
|
|
3077
|
+
outcome: "failed",
|
|
3078
|
+
reason: timedOut ? `exceeded its ${budget} ms budget` : messageOf(e)
|
|
3079
|
+
};
|
|
3080
|
+
} finally {
|
|
3081
|
+
clearTimeout(timer);
|
|
3082
|
+
cancelled.removeEventListener("abort", cancelParent);
|
|
3083
|
+
}
|
|
3084
|
+
};
|
|
3085
|
+
const batch = await runVisionBatch({
|
|
3086
|
+
images: payload,
|
|
3087
|
+
concurrency: clampVisionConcurrency(this.#config.visionConcurrency),
|
|
3088
|
+
describe: describeOne,
|
|
3089
|
+
signal: cancelled,
|
|
3090
|
+
onProgress: (progressed, total) => {
|
|
3091
|
+
this.#lifecycle({
|
|
3092
|
+
phase: "execute",
|
|
3093
|
+
data: {
|
|
3094
|
+
kind: "vision-progress",
|
|
3095
|
+
source: call.name,
|
|
3096
|
+
done: progressed,
|
|
3097
|
+
total
|
|
3098
|
+
}
|
|
3099
|
+
}, state);
|
|
3100
|
+
}
|
|
3101
|
+
});
|
|
3102
|
+
if (batch.declined) return finish(`The user declined to send ${subject} to another provider for description. ${dropped}`);
|
|
3103
|
+
const describedCount = batch.described.filter((text) => text !== void 0).length;
|
|
3104
|
+
if (describedCount === 0) return finish(`Due to image recognition errors, ${subject} could not be read: ${batch.failures[0]?.reason ?? "no description was produced"}. ${dropped}`);
|
|
3105
|
+
const lines = batch.described.map((text, index) => {
|
|
3106
|
+
const failure = batch.failures.find((entry) => entry.index === index);
|
|
3107
|
+
const body = text ?? `not read due to image recognition errors: ${failure?.reason ?? "the endpoint gave no reason"}`;
|
|
3108
|
+
return images.length === 1 ? body : `[${index + 1}/${images.length}] ${body}`;
|
|
3109
|
+
});
|
|
3110
|
+
if (describedCount < images.length) state.trace.record("run.warning", {
|
|
3111
|
+
message: `Due to image recognition errors, ${images.length - describedCount} of ${images.length} images from "${call.name}" were not read.`,
|
|
3112
|
+
data: {
|
|
3113
|
+
images: images.length,
|
|
3114
|
+
described: describedCount,
|
|
3115
|
+
tool: call.name
|
|
3116
|
+
}
|
|
3117
|
+
});
|
|
3118
|
+
return {
|
|
3119
|
+
kept,
|
|
3120
|
+
note: `--- ${images.length === 1 ? "Image" : `${images.length} images`} from "${call.name}", described by ${batch.endpoint ?? "another model"} because the assistant model cannot read images ---\n${lines.join("\n")}`
|
|
3121
|
+
};
|
|
3122
|
+
}
|
|
2814
3123
|
/** 单个 PDF 分片 → 文本;包装形态与 chatEngine 的附件标签同口径(FR-22.3 / FR-22.4) */
|
|
2815
3124
|
async #pdfAsText(state, part, extract) {
|
|
2816
3125
|
const name = part.name ?? "document.pdf";
|
|
@@ -2906,6 +3215,12 @@ var AgentLoop = class {
|
|
|
2906
3215
|
state.trace.record("run.warning", { message: note });
|
|
2907
3216
|
notes.push(note);
|
|
2908
3217
|
}
|
|
3218
|
+
const delegated = await this.#delegateVision(state, call, carried);
|
|
3219
|
+
if (delegated !== void 0) {
|
|
3220
|
+
carried.length = 0;
|
|
3221
|
+
carried.push(...delegated.kept);
|
|
3222
|
+
notes.push(delegated.note);
|
|
3223
|
+
}
|
|
2909
3224
|
return {
|
|
2910
3225
|
tool: [
|
|
2911
3226
|
{
|
|
@@ -3193,6 +3508,7 @@ var AgentLoop = class {
|
|
|
3193
3508
|
confirm: (message) => this.#confirm(message, state),
|
|
3194
3509
|
...this.#deps.fetchData ? { fetchData: this.#deps.fetchData } : {},
|
|
3195
3510
|
...this.#deps.documentSurface ? { documentSurface: true } : {},
|
|
3511
|
+
...this.#deps.writeSpreadsheet ? { writeSpreadsheet: true } : {},
|
|
3196
3512
|
...this.#deps.uploadFiles ? { uploadFiles: this.#uploadFilePort(this.#deps.uploadFiles, skillName, state) } : {},
|
|
3197
3513
|
onWarning: (message) => state.trace.record("run.warning", { message }),
|
|
3198
3514
|
onArtifactCreated: (artifact) => createdIds.add(artifact.id)
|
|
@@ -3535,6 +3851,148 @@ var AgentLoop = class {
|
|
|
3535
3851
|
}
|
|
3536
3852
|
};
|
|
3537
3853
|
|
|
3854
|
+
//#endregion
|
|
3855
|
+
//#region ../runtime/src/engine/compaction.ts
|
|
3856
|
+
/** 摘要消息的固定前缀:压缩留痕靠它识别,UI 与后续压缩都读这个标记 */
|
|
3857
|
+
const COMPACTION_PREFIX = "[Earlier conversation summary]";
|
|
3858
|
+
/** 触发比例默认值:留出四分之一余量,够装下这一轮的提问与工具结果 */
|
|
3859
|
+
const DEFAULT_COMPACTION_THRESHOLD = .75;
|
|
3860
|
+
/** 压缩后至少保留的原文消息数 */
|
|
3861
|
+
const DEFAULT_KEEP_RECENT_MESSAGES = 12;
|
|
3862
|
+
/** 保留原文的下界:压到比这更少就等于换了个会话(FR-20.6 第 3 条) */
|
|
3863
|
+
const MIN_KEEP_RECENT_MESSAGES = 6;
|
|
3864
|
+
/**
|
|
3865
|
+
* 要不要压缩。三个输入缺一不压(FR-20.6 第 4 条的「不猜」):
|
|
3866
|
+
* 没有窗口就没有分母,没有真实用量就没有分子——两者都不许估算。
|
|
3867
|
+
*/
|
|
3868
|
+
function shouldCompact(lastInputTokens, contextWindow, threshold) {
|
|
3869
|
+
if (lastInputTokens === void 0 || contextWindow === void 0) return false;
|
|
3870
|
+
if (!Number.isFinite(lastInputTokens) || !Number.isFinite(contextWindow) || contextWindow <= 0) return false;
|
|
3871
|
+
return lastInputTokens / contextWindow >= threshold;
|
|
3872
|
+
}
|
|
3873
|
+
/**
|
|
3874
|
+
* 把想要的切点挪到不破坏 tool 契约的位置:
|
|
3875
|
+
* 切点之后不得以孤儿 `tool` 消息开头(它的 assistant 调用会被压掉),
|
|
3876
|
+
* 切点之前不得以未应答的 assistant `toolCalls` 结尾。
|
|
3877
|
+
*
|
|
3878
|
+
* 与 `trimSessionHistory` 是同一套边界规则,区别只在这里要的是**一个**切点。
|
|
3879
|
+
* 返回 0 表示没有安全切点,调用方应当放弃本次压缩。
|
|
3880
|
+
*/
|
|
3881
|
+
function alignCut(messages, desired) {
|
|
3882
|
+
let cut = Math.max(0, Math.min(desired, messages.length));
|
|
3883
|
+
while (cut < messages.length && messages[cut].role === "tool") cut += 1;
|
|
3884
|
+
if (cut >= messages.length) return 0;
|
|
3885
|
+
while (cut > 0) {
|
|
3886
|
+
const last = messages[cut - 1];
|
|
3887
|
+
if (last.role === "assistant" && last.toolCalls?.length) cut -= 1;
|
|
3888
|
+
else break;
|
|
3889
|
+
}
|
|
3890
|
+
return cut;
|
|
3891
|
+
}
|
|
3892
|
+
/** 把一段消息压成给模型看的纯文本转录;图片与文件只留占位,不塞 base64 */
|
|
3893
|
+
function transcribe(messages) {
|
|
3894
|
+
return messages.map((message) => {
|
|
3895
|
+
const body = message.content.map((part) => {
|
|
3896
|
+
if (part.type === "text") return part.text;
|
|
3897
|
+
if (part.type === "image") return "[image]";
|
|
3898
|
+
return `[file${part.name !== void 0 ? ` ${part.name}` : ""}]`;
|
|
3899
|
+
}).join("\n").trim();
|
|
3900
|
+
const calls = message.toolCalls?.map((call) => `[called ${call.name}]`).join(" ") ?? "";
|
|
3901
|
+
return `${message.role.toUpperCase()}: ${[body, calls].filter((s) => s !== "").join(" ")}`.trim();
|
|
3902
|
+
}).join("\n");
|
|
3903
|
+
}
|
|
3904
|
+
const SUMMARY_INSTRUCTION = "Summarize the conversation transcript below so that another assistant can continue the conversation without seeing the original messages. Keep every fact, decision, user preference, identifier, number and unfinished task. Drop greetings, acknowledgements and repetition. Write plain prose, no headings, no bullet lists, under 400 words. Do not add information that is not in the transcript.";
|
|
3905
|
+
/**
|
|
3906
|
+
* 压缩会话历史:最早的一段换成一条摘要消息,最近若干条保留原文。
|
|
3907
|
+
*
|
|
3908
|
+
* 返回 `undefined` 表示**没压**——历史还不够长、找不到安全切点、或摘要调用失败。
|
|
3909
|
+
* 压缩是优化,任何一步不顺都退回原历史,不能把一次正常提问变成一次失败(FR-20.6 第 5 条)。
|
|
3910
|
+
*
|
|
3911
|
+
* 摘要用 `user` 角色而不是 `system`:会话历史里本就不含 system(每轮 run 由路由重起),
|
|
3912
|
+
* 且 Anthropic 不接受非首条的 system 消息。
|
|
3913
|
+
*/
|
|
3914
|
+
async function compactHistory(history, options) {
|
|
3915
|
+
const keep = Math.max(6, options.keepRecentMessages ?? 12);
|
|
3916
|
+
const cut = alignCut(history, history.length - keep);
|
|
3917
|
+
if (cut <= 0) return void 0;
|
|
3918
|
+
const head = history.slice(0, cut);
|
|
3919
|
+
const tail = history.slice(cut);
|
|
3920
|
+
let summary;
|
|
3921
|
+
try {
|
|
3922
|
+
summary = ((await options.llm.complete({ messages: [{
|
|
3923
|
+
role: "system",
|
|
3924
|
+
content: [{
|
|
3925
|
+
type: "text",
|
|
3926
|
+
text: SUMMARY_INSTRUCTION
|
|
3927
|
+
}]
|
|
3928
|
+
}, {
|
|
3929
|
+
role: "user",
|
|
3930
|
+
content: [{
|
|
3931
|
+
type: "text",
|
|
3932
|
+
text: transcribe(head)
|
|
3933
|
+
}]
|
|
3934
|
+
}] })).content ?? []).map((part) => part.type === "text" ? part.text : "").join("").trim();
|
|
3935
|
+
} catch (e) {
|
|
3936
|
+
options.onWarn?.(`compaction summary request failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
3937
|
+
return;
|
|
3938
|
+
}
|
|
3939
|
+
if (summary === "") {
|
|
3940
|
+
options.onWarn?.("compaction summary was empty; history left untouched");
|
|
3941
|
+
return;
|
|
3942
|
+
}
|
|
3943
|
+
return {
|
|
3944
|
+
messages: [{
|
|
3945
|
+
role: "user",
|
|
3946
|
+
content: [{
|
|
3947
|
+
type: "text",
|
|
3948
|
+
text: `${COMPACTION_PREFIX}\n${summary}`
|
|
3949
|
+
}]
|
|
3950
|
+
}, ...tail],
|
|
3951
|
+
removed: head.length,
|
|
3952
|
+
kept: tail.length
|
|
3953
|
+
};
|
|
3954
|
+
}
|
|
3955
|
+
|
|
3956
|
+
//#endregion
|
|
3957
|
+
//#region ../runtime/src/engine/usage.ts
|
|
3958
|
+
/**
|
|
3959
|
+
* 从 run 的 trace 聚合大模型用量(请求次数 + 输入/输出 token)。
|
|
3960
|
+
* 与 `summarizeToolCalls` 同型:消费方不再各自遍历 trace。
|
|
3961
|
+
* @stable
|
|
3962
|
+
*/
|
|
3963
|
+
function summarizeRunUsage(trace) {
|
|
3964
|
+
let llmCalls = 0;
|
|
3965
|
+
let inputTokens = 0;
|
|
3966
|
+
let outputTokens = 0;
|
|
3967
|
+
let hasUsage = false;
|
|
3968
|
+
let lastInputTokens;
|
|
3969
|
+
for (const event of trace) {
|
|
3970
|
+
if (event.type === "llm.request") {
|
|
3971
|
+
llmCalls += 1;
|
|
3972
|
+
continue;
|
|
3973
|
+
}
|
|
3974
|
+
if (event.type !== "llm.usage") continue;
|
|
3975
|
+
const input = event.data?.["inputTokens"];
|
|
3976
|
+
const output = event.data?.["outputTokens"];
|
|
3977
|
+
const hasInput = typeof input === "number" && Number.isFinite(input);
|
|
3978
|
+
const hasOutput = typeof output === "number" && Number.isFinite(output);
|
|
3979
|
+
if (hasInput) {
|
|
3980
|
+
inputTokens += input;
|
|
3981
|
+
lastInputTokens = input;
|
|
3982
|
+
}
|
|
3983
|
+
if (hasOutput) outputTokens += output;
|
|
3984
|
+
if (hasInput || hasOutput) hasUsage = true;
|
|
3985
|
+
}
|
|
3986
|
+
return {
|
|
3987
|
+
llmCalls,
|
|
3988
|
+
...hasUsage ? {
|
|
3989
|
+
inputTokens,
|
|
3990
|
+
outputTokens
|
|
3991
|
+
} : {},
|
|
3992
|
+
...lastInputTokens !== void 0 ? { lastInputTokens } : {}
|
|
3993
|
+
};
|
|
3994
|
+
}
|
|
3995
|
+
|
|
3538
3996
|
//#endregion
|
|
3539
3997
|
//#region ../runtime/src/engine/runtime.ts
|
|
3540
3998
|
/**
|
|
@@ -3604,24 +4062,46 @@ var WebSkillRuntime = class {
|
|
|
3604
4062
|
* 既有 runtime.run(prompt) 保持无状态单次语义不变。
|
|
3605
4063
|
* 同 handle 并发 run 经 per-handle 队列串行化(防历史 last-writer-wins 丢轮次);
|
|
3606
4064
|
* maxHistoryMessages(默认取 `DEFAULT_LOOP_LIMITS`)超出时滚动裁剪中段(保留首尾;边界对齐 tool 契约)。
|
|
4065
|
+
*
|
|
4066
|
+
* 配了 `contextWindow` 时另加一道**主动压缩**(分册 20 FR-20.6):按上一轮的真实
|
|
4067
|
+
* `prompt_tokens` 与窗口的比值判定,超阈值就在下一轮发起前把最早的一段摘要掉。
|
|
4068
|
+
* 三个输入(窗口、用量、足够长的历史)缺一不压——不估算、不猜。
|
|
3607
4069
|
*/
|
|
3608
4070
|
createSession(options = {}) {
|
|
3609
4071
|
const sessionId = options.sessionId ?? `session-${Math.random().toString(36).slice(2, 10)}`;
|
|
3610
4072
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3611
4073
|
const maxHistory = options.maxHistoryMessages ?? DEFAULT_LOOP_LIMITS.maxHistoryMessages;
|
|
4074
|
+
const threshold = options.compactionThreshold ?? .75;
|
|
3612
4075
|
let history = [];
|
|
3613
4076
|
let queue = Promise.resolve();
|
|
4077
|
+
/** 上一轮结束时的上下文水位线;没拿到 usage 就一直是 undefined,届时不压缩 */
|
|
4078
|
+
let lastInputTokens;
|
|
3614
4079
|
return {
|
|
3615
4080
|
id: sessionId,
|
|
3616
4081
|
createdAt,
|
|
3617
4082
|
run: (prompt, runOptions = {}) => {
|
|
3618
4083
|
const result = queue.then(async () => {
|
|
4084
|
+
if (shouldCompact(lastInputTokens, options.contextWindow, threshold)) {
|
|
4085
|
+
const compacted = await compactHistory(history, {
|
|
4086
|
+
llm: this.#deps.llm,
|
|
4087
|
+
...options.keepRecentMessages !== void 0 ? { keepRecentMessages: options.keepRecentMessages } : {}
|
|
4088
|
+
});
|
|
4089
|
+
if (compacted) {
|
|
4090
|
+
history = compacted.messages;
|
|
4091
|
+
lastInputTokens = void 0;
|
|
4092
|
+
options.onCompacted?.({
|
|
4093
|
+
removed: compacted.removed,
|
|
4094
|
+
kept: compacted.kept
|
|
4095
|
+
});
|
|
4096
|
+
}
|
|
4097
|
+
}
|
|
3619
4098
|
const r = await this.run(prompt, {
|
|
3620
4099
|
sessionId,
|
|
3621
4100
|
history,
|
|
3622
4101
|
...runOptions
|
|
3623
4102
|
});
|
|
3624
4103
|
history = trimSessionHistory(r.messages.slice(1), maxHistory);
|
|
4104
|
+
lastInputTokens = summarizeRunUsage(r.run.trace).lastInputTokens;
|
|
3625
4105
|
return r;
|
|
3626
4106
|
});
|
|
3627
4107
|
queue = result.then(() => void 0, () => void 0);
|
|
@@ -3686,8 +4166,10 @@ var WebSkillRuntime = class {
|
|
|
3686
4166
|
docxExtractor: this.#deps.docxExtractor,
|
|
3687
4167
|
xlsxExtractor: this.#deps.xlsxExtractor,
|
|
3688
4168
|
pdfExtractor: this.#deps.pdfExtractor,
|
|
4169
|
+
visionDelegate: this.#deps.visionDelegate,
|
|
3689
4170
|
documentAudit: this.#deps.documentAudit,
|
|
3690
4171
|
documentSurface: this.#deps.documentSurface,
|
|
4172
|
+
writeSpreadsheet: this.#deps.writeSpreadsheet,
|
|
3691
4173
|
uploadFileAudit: this.#deps.uploadFileAudit,
|
|
3692
4174
|
maxUploadFileBytes: this.#deps.maxUploadFileBytes,
|
|
3693
4175
|
uploadFiles: options.uploadFiles
|
|
@@ -3819,8 +4301,10 @@ var WebSkillRuntime = class {
|
|
|
3819
4301
|
docxExtractor: this.#deps.docxExtractor,
|
|
3820
4302
|
xlsxExtractor: this.#deps.xlsxExtractor,
|
|
3821
4303
|
pdfExtractor: this.#deps.pdfExtractor,
|
|
4304
|
+
visionDelegate: this.#deps.visionDelegate,
|
|
3822
4305
|
documentAudit: this.#deps.documentAudit,
|
|
3823
4306
|
documentSurface: this.#deps.documentSurface,
|
|
4307
|
+
writeSpreadsheet: this.#deps.writeSpreadsheet,
|
|
3824
4308
|
uploadFileAudit: this.#deps.uploadFileAudit,
|
|
3825
4309
|
maxUploadFileBytes: this.#deps.maxUploadFileBytes
|
|
3826
4310
|
}, this.#deps.config);
|
|
@@ -3979,6 +4463,9 @@ var FsArtifactStore = class {
|
|
|
3979
4463
|
return [];
|
|
3980
4464
|
}
|
|
3981
4465
|
}
|
|
4466
|
+
async readArtifact(runId, path) {
|
|
4467
|
+
return this.#fs.readBinary(this.#artifactPath(runId, path));
|
|
4468
|
+
}
|
|
3982
4469
|
#artifactPath(runId, artifactPath) {
|
|
3983
4470
|
assertSafePathSegment(runId, "runId");
|
|
3984
4471
|
return resolveInsideRoot(`${this.#root}/${runId}`, artifactPath);
|
|
@@ -4030,6 +4517,17 @@ function parseBridgeRequest(data) {
|
|
|
4030
4517
|
..."metadata" in data ? { metadata: data["metadata"] } : {}
|
|
4031
4518
|
};
|
|
4032
4519
|
}
|
|
4520
|
+
case "writeSpreadsheet": {
|
|
4521
|
+
const { path, spec } = data;
|
|
4522
|
+
if (!isNonEmptyString(path) || !isRecord(spec)) return void 0;
|
|
4523
|
+
return {
|
|
4524
|
+
kind,
|
|
4525
|
+
id,
|
|
4526
|
+
path,
|
|
4527
|
+
spec,
|
|
4528
|
+
..."metadata" in data ? { metadata: data["metadata"] } : {}
|
|
4529
|
+
};
|
|
4530
|
+
}
|
|
4033
4531
|
case "confirm": return isNonEmptyString(data["message"]) ? {
|
|
4034
4532
|
kind,
|
|
4035
4533
|
id,
|
|
@@ -4255,4 +4753,4 @@ var CapabilityApproval = class CapabilityApproval {
|
|
|
4255
4753
|
};
|
|
4256
4754
|
|
|
4257
4755
|
//#endregion
|
|
4258
|
-
export {
|
|
4756
|
+
export { READ_SKILL_FILE_TOOL as $, BEHAVIOR_RECORDS_KEY as A, renderUserProfileContext as B, extractTodoTraceEvents as C, VISION_SLOW_THRESHOLD_MS as D, VISION_MAX_CONCURRENCY as E, readProfileEntries as F, schemaToForm as G, EMPTY_USER_PROFILE as H, readUserProfile as I, ASK_USER_INPUT_SCHEMA as J, createScriptContext as K, sampleBehaviorRecords as L, appendBehaviorRecords as M, mergeProfileEntries as N, clampVisionConcurrency as O, readBehaviorRecords as P, READ_SKILL_FILE_INPUT_SCHEMA as Q, USER_PROFILE_NO_INVENTION_RULE as R, extractSkillCandidate as S, VISION_DEFAULT_CONCURRENCY as T, SerializingMemoryStore as U, DEFAULT_USER_PROFILE_LIMITS as V, EventBus as W, ASK_USER_TOOL as X, ASK_USER_MAX_FIELDS as Y, ASK_USER_TOOL_NAME as Z, FsRunSnapshotStore as _, networkPolicyLibSource as a, scriptToolName as at, DEFAULT_LOOP_LIMITS as b, bridgeError as c, ProgressiveRouter as ct, FsMemoryStore as d, xmlRenderer as dt, READ_SKILL_FILE_TOOL_NAME as et, WebSkillRuntime as f, redactToolStepArgs as g, SENSITIVE_ANNOTATION as h, isNetworkAllowed as i, schemaSourceLabel as it, USER_PROFILE_KEY as j, runVisionBatch as k, parseBridgeRequest as l, escapeXml as lt, AgentLoop as m, normalizeErrorCode as n, formatSkillScriptManifest as nt, networkUrlHost as o, resolveToolName as ot, summarizeRunUsage as p, ASK_USER_FIELD_TYPES as q, normalizeToolError as r, listSkillScripts as rt, UPLOAD_FILES_UNAVAILABLE as s, toLlmToolSpec as st, CapabilityApproval as t, ALLOWED_TOOLS_EXCLUSION_REASON as tt, FsArtifactStore as u, renderAvailableSkillsXml as ut, RUN_SNAPSHOT_SCHEMA_VERSION as v, TraceRecorder as w, MAX_TOOL_STEP_ARG_BYTES as x, isUnsupportedRunSnapshot as y, USER_PROFILE_PROMPT_HEADER as z };
|