@webskill/sdk 0.8.0 → 0.10.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/agent.d.ts +2 -2
- package/dist/agent.js +6 -2
- package/dist/browser.d.ts +5 -3
- package/dist/browser.js +35 -13
- package/dist/{catalogComponents-DfxxfUvn-D55Gbb2l.js → catalogComponents-DTcYfpLQ-CcoOaz-Z.js} +1946 -976
- package/dist/{dist-CJqQsIm9.js → dist-1OFC-zax.js} +7 -5
- package/dist/{dist-DmI5SBBF.js → dist-BViUeszk.js} +393 -44
- package/dist/{dist-59XlqDuv.js → dist-Bev6i6Ip.js} +10 -2
- package/dist/{eventTypes-g1BXL6x5-CibcOftR.js → eventTypes-s2uwAcLG-Go3l_dUe.js} +13 -2
- package/dist/governance.d.ts +37 -6
- package/dist/governance.js +53 -15
- package/dist/{index-P9J2LTfU.d.ts → index-B0QPLWPZ.d.ts} +6 -2
- package/dist/{index-fLskQfAS.d.ts → index-BF4E1a9j.d.ts} +10 -3
- package/dist/{index-K-eewlGL.d.ts → index-DtFdMKBX.d.ts} +69 -7
- package/dist/index.d.ts +11 -3
- package/dist/index.js +13 -4
- package/dist/mcp.d.ts +23 -3
- package/dist/mcp.js +70 -25
- package/dist/{memoryArtifactStore-52Zn9npI-upv5OWYf.js → memoryArtifactStore-52Zn9npI-LbCQaqyx.js} +1 -1
- package/dist/node.d.ts +3 -3
- package/dist/node.js +6 -6
- package/dist/{openUiLibrary-DURlAxjk-CU6AzfSW.js → openUiLibrary-CIrV--Ad-B8zG_91e.js} +3 -3
- package/dist/{skillVersionStore-Bl-ElD45-gRfSaAby.d.ts → skillVersionStore-D-qHk9ZE-DBsYYCWn.d.ts} +13 -5
- package/dist/{testing-BCUO5gZR.js → testing-WPTyXQYt.js} +28 -5
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +2 -2
- package/dist/{types-B3n0cMZu-BdcqQ35O.d.ts → types-CrRcT-LM-DZAp8sWv.d.ts} +44 -3
- package/dist/ui-react.d.ts +17 -2
- package/dist/ui-react.js +123 -39
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui-vue.js +1 -1
- package/dist/ui.d.ts +3 -3
- package/dist/ui.js +2 -2
- package/dist/{webskillLitCatalog-DwTwSBFt-DiXXpNZA.js → webskillLitCatalog-BJrphK0y-SGmRXaaO.js} +2 -2
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as textParts, i as rejectUnsupportedPart, n as partsToText, o as validateLlmMessages, r as promptText, t as MemoryArtifactStore } from "./memoryArtifactStore-52Zn9npI-
|
|
1
|
+
import { K as validateSkills, M as messageOf, P as parseSkillMarkdown, R as renderAvailableSkillsXml, V as resolveInsideRoot, _ as assertSafePathSegment, h as WebSkillError, m as SkillReader, p as SkillDiscovery, v as atomicWriteText, y as buildCatalog } from "./dist-Bev6i6Ip.js";
|
|
2
|
+
import { a as textParts, i as rejectUnsupportedPart, n as partsToText, o as validateLlmMessages, r as promptText, t as MemoryArtifactStore } from "./memoryArtifactStore-52Zn9npI-LbCQaqyx.js";
|
|
3
3
|
|
|
4
4
|
//#region ../runtime/dist/index.js
|
|
5
5
|
/** 与正常工具结果同构:模型读到的是「这次调用被中断了」,而不是一个凭空消失的调用 */
|
|
@@ -103,11 +103,11 @@ const SCHEMA_KEYS = /* @__PURE__ */ new Set([
|
|
|
103
103
|
"then",
|
|
104
104
|
"else"
|
|
105
105
|
]);
|
|
106
|
-
function isRecord$
|
|
106
|
+
function isRecord$7(value) {
|
|
107
107
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
108
108
|
}
|
|
109
109
|
function isSelfRef(value) {
|
|
110
|
-
return isRecord$
|
|
110
|
+
return isRecord$7(value) && value["$ref"] === SELF_REF;
|
|
111
111
|
}
|
|
112
112
|
/**
|
|
113
113
|
* 按 JSON Schema 关键字下降到子 schema。
|
|
@@ -117,7 +117,7 @@ function isSelfRef(value) {
|
|
|
117
117
|
function forEachSubSchema(node, visit) {
|
|
118
118
|
for (const [key, value] of Object.entries(node)) {
|
|
119
119
|
if (SCHEMA_MAP_KEYS.has(key)) {
|
|
120
|
-
if (isRecord$
|
|
120
|
+
if (isRecord$7(value)) for (const name of Object.keys(value)) visit(value[name], [key, name]);
|
|
121
121
|
continue;
|
|
122
122
|
}
|
|
123
123
|
if (SCHEMA_LIST_KEYS.has(key)) {
|
|
@@ -136,7 +136,7 @@ function pathKey(path) {
|
|
|
136
136
|
* `#` 解析到最近的带 `$id` 的祖先;没有则是文档根。
|
|
137
137
|
*/
|
|
138
138
|
function collectSelfRefRoots(node, path, resourceRoot, out) {
|
|
139
|
-
if (!isRecord$
|
|
139
|
+
if (!isRecord$7(node)) return;
|
|
140
140
|
if (isSelfRef(node)) {
|
|
141
141
|
out.set(pathKey(resourceRoot), resourceRoot);
|
|
142
142
|
return;
|
|
@@ -149,7 +149,7 @@ function collectSelfRefRoots(node, path, resourceRoot, out) {
|
|
|
149
149
|
function getAt(schema, path) {
|
|
150
150
|
let current = schema;
|
|
151
151
|
for (const segment of path) if (Array.isArray(current) && typeof segment === "number") current = current[segment];
|
|
152
|
-
else if (isRecord$
|
|
152
|
+
else if (isRecord$7(current) && typeof segment === "string") current = current[segment];
|
|
153
153
|
else return void 0;
|
|
154
154
|
return current;
|
|
155
155
|
}
|
|
@@ -175,7 +175,7 @@ function setAt(schema, path, replacement) {
|
|
|
175
175
|
* 正是严格解析器无法解析 `#` 的直接原因。
|
|
176
176
|
*/
|
|
177
177
|
function rewriteSelfRefs(node, target, depth) {
|
|
178
|
-
if (!isRecord$
|
|
178
|
+
if (!isRecord$7(node)) return node;
|
|
179
179
|
if (depth > 8) throw new WebSkillError("TOOL_SCHEMA_UNAVAILABLE", `Tool schema nesting exceeds 8 levels; cannot produce a portable form`);
|
|
180
180
|
if (isSelfRef(node)) {
|
|
181
181
|
const rest = {};
|
|
@@ -188,7 +188,7 @@ function rewriteSelfRefs(node, target, depth) {
|
|
|
188
188
|
const result = {};
|
|
189
189
|
for (const [key, value] of Object.entries(node)) {
|
|
190
190
|
if (key === "$id" || key === "$schema") continue;
|
|
191
|
-
if (SCHEMA_MAP_KEYS.has(key) && isRecord$
|
|
191
|
+
if (SCHEMA_MAP_KEYS.has(key) && isRecord$7(value)) {
|
|
192
192
|
const mapped = {};
|
|
193
193
|
for (const [name, child] of Object.entries(value)) mapped[name] = rewriteSelfRefs(child, target, depth + 1);
|
|
194
194
|
result[key] = mapped;
|
|
@@ -226,8 +226,8 @@ function toPortableToolSchema(schema) {
|
|
|
226
226
|
if (roots.size > 1) throw new WebSkillError("TOOL_SCHEMA_UNAVAILABLE", "Tool schema contains self references in more than one schema resource; cannot produce a portable form");
|
|
227
227
|
const rootPath = [...roots.values()][0] ?? [];
|
|
228
228
|
const resource = getAt(schema, rootPath);
|
|
229
|
-
if (!isRecord$
|
|
230
|
-
const existingDefs = isRecord$
|
|
229
|
+
if (!isRecord$7(resource)) return schema;
|
|
230
|
+
const existingDefs = isRecord$7(schema[DEFS_KEY]) ? schema[DEFS_KEY] : void 0;
|
|
231
231
|
const taken = new Set(Object.keys(existingDefs ?? {}));
|
|
232
232
|
const nodeName = uniqueName(NODE_NAME, taken);
|
|
233
233
|
taken.add(nodeName);
|
|
@@ -258,6 +258,63 @@ function toPortableToolSchema(schema) {
|
|
|
258
258
|
}
|
|
259
259
|
};
|
|
260
260
|
}
|
|
261
|
+
function isRecord$6(value) {
|
|
262
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* 与 `interaction/schemaToForm` 的 file 判定保持一致:JSON Schema 里二进制内容的
|
|
266
|
+
* 两种标准写法(`format: binary` / `contentEncoding: base64`)。
|
|
267
|
+
*/
|
|
268
|
+
function isModelUnfillable(node) {
|
|
269
|
+
return isRecord$6(node) && node["type"] === "string" && (node["format"] === "binary" || node["contentEncoding"] === "base64");
|
|
270
|
+
}
|
|
271
|
+
function stripNode(node) {
|
|
272
|
+
if (Array.isArray(node)) return node.map(stripNode);
|
|
273
|
+
if (!isRecord$6(node)) return node;
|
|
274
|
+
const out = {};
|
|
275
|
+
for (const [key, value] of Object.entries(node)) {
|
|
276
|
+
if (SCHEMA_MAP_KEYS.has(key) && isRecord$6(value)) {
|
|
277
|
+
const mapped = {};
|
|
278
|
+
for (const [name, child] of Object.entries(value)) {
|
|
279
|
+
if (key === "properties" && isModelUnfillable(child)) continue;
|
|
280
|
+
mapped[name] = stripNode(child);
|
|
281
|
+
}
|
|
282
|
+
out[key] = mapped;
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (SCHEMA_LIST_KEYS.has(key) && Array.isArray(value)) {
|
|
286
|
+
out[key] = value.map(stripNode);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (SCHEMA_KEYS.has(key)) {
|
|
290
|
+
out[key] = stripNode(value);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
out[key] = value;
|
|
294
|
+
}
|
|
295
|
+
const required = out["required"];
|
|
296
|
+
const properties = out["properties"];
|
|
297
|
+
if (Array.isArray(required) && isRecord$6(properties)) out["required"] = required.filter((name) => typeof name !== "string" || name in properties);
|
|
298
|
+
return out;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* 把模型不可能自己产出的字段(本地文件二进制)从**出站**工具 schema 里剥离。
|
|
302
|
+
*
|
|
303
|
+
* 模型拿不到用户磁盘上的文件,留着这个字段只有两种下场:模型拒绝调用工具
|
|
304
|
+
* (实测 DeepSeek 直接回文本「请上传文件」,file-pick 永远触发不了),或编造一个
|
|
305
|
+
* 占位字符串把技能跑挂。剥掉后模型以缺参形式调用,引擎的 missing-params 链路
|
|
306
|
+
* 会弹 file-pick 让用户用系统文件选择器补这个字段。
|
|
307
|
+
*
|
|
308
|
+
* 这是**传输层适配**,只应在出站请求体上调用;不要下沉到 AgentLoop——
|
|
309
|
+
* 引擎侧的 missing 校验看的必须是技能作者写的完整 inputSchema。
|
|
310
|
+
*
|
|
311
|
+
* 已知边界(0.10.0 复核注记):只剥 `properties` 里的 inline 二进制字段;
|
|
312
|
+
* 通过 `$ref` 指向 `$defs` 中二进制定义的字段仍会下发(剥掉会留悬空 $ref),
|
|
313
|
+
* 这类技能依旧可能拿不到 file-pick——需要完整修复时再补「剥 $def + 剥指向它的 $ref」。
|
|
314
|
+
*/
|
|
315
|
+
function stripModelUnfillableParams(schema) {
|
|
316
|
+
return stripNode(schema);
|
|
317
|
+
}
|
|
261
318
|
/** 各家都认的基础关键字。Google `FunctionDeclaration.parameters` 是其中最窄的一家。 */
|
|
262
319
|
const BASE_KEYWORDS = /* @__PURE__ */ new Set([
|
|
263
320
|
"type",
|
|
@@ -499,7 +556,7 @@ const toOpenAiTools = (tools) => tools.map((tool) => ({
|
|
|
499
556
|
function: {
|
|
500
557
|
name: tool.name,
|
|
501
558
|
...tool.description ? { description: tool.description } : {},
|
|
502
|
-
parameters: sanitizeForVendor(toPortableToolSchema(tool.inputSchema), OPENAI_SCHEMA_PROFILE, tool.name)
|
|
559
|
+
parameters: sanitizeForVendor(toPortableToolSchema(stripModelUnfillableParams(tool.inputSchema)), OPENAI_SCHEMA_PROFILE, tool.name)
|
|
503
560
|
}
|
|
504
561
|
}));
|
|
505
562
|
const errorMessage$2 = (e) => e instanceof Error ? e.message : String(e);
|
|
@@ -539,6 +596,7 @@ var OpenAiCompatibleClient = class {
|
|
|
539
596
|
const reader = res.body.getReader();
|
|
540
597
|
const frames = createSseFrameReader();
|
|
541
598
|
let done = false;
|
|
599
|
+
let usage;
|
|
542
600
|
const handleFrame = function* (data) {
|
|
543
601
|
if (data === "[DONE]") {
|
|
544
602
|
done = true;
|
|
@@ -550,8 +608,17 @@ var OpenAiCompatibleClient = class {
|
|
|
550
608
|
} catch (e) {
|
|
551
609
|
throw new WebSkillError("LLM_REQUEST_FAILED", `Failed to parse LLM stream frame: ${errorMessage$2(e)}`, data);
|
|
552
610
|
}
|
|
611
|
+
const usageRaw = chunk.usage;
|
|
612
|
+
if (usageRaw && (typeof usageRaw["prompt_tokens"] === "number" || typeof usageRaw["completion_tokens"] === "number")) usage = {
|
|
613
|
+
inputTokens: typeof usageRaw["prompt_tokens"] === "number" ? usageRaw["prompt_tokens"] : 0,
|
|
614
|
+
outputTokens: typeof usageRaw["completion_tokens"] === "number" ? usageRaw["completion_tokens"] : 0
|
|
615
|
+
};
|
|
553
616
|
const delta = chunk.choices?.[0]?.delta;
|
|
554
617
|
if (!delta) return;
|
|
618
|
+
if (typeof delta["reasoning_content"] === "string" && delta["reasoning_content"] !== "") yield {
|
|
619
|
+
type: "thinking-delta",
|
|
620
|
+
delta: delta["reasoning_content"]
|
|
621
|
+
};
|
|
555
622
|
if (typeof delta["content"] === "string" && delta["content"] !== "") yield {
|
|
556
623
|
type: "text-delta",
|
|
557
624
|
delta: delta["content"]
|
|
@@ -601,7 +668,10 @@ var OpenAiCompatibleClient = class {
|
|
|
601
668
|
};
|
|
602
669
|
})
|
|
603
670
|
};
|
|
604
|
-
yield {
|
|
671
|
+
yield {
|
|
672
|
+
type: "done",
|
|
673
|
+
...usage ? { usage } : {}
|
|
674
|
+
};
|
|
605
675
|
}
|
|
606
676
|
async #postChat(input, stream) {
|
|
607
677
|
const { baseUrl, apiKey, model } = this.#requireConfig();
|
|
@@ -615,7 +685,10 @@ var OpenAiCompatibleClient = class {
|
|
|
615
685
|
body["tool_choice"] = "auto";
|
|
616
686
|
}
|
|
617
687
|
if (input.temperature !== void 0) body["temperature"] = input.temperature;
|
|
618
|
-
if (stream)
|
|
688
|
+
if (stream) {
|
|
689
|
+
body["stream"] = true;
|
|
690
|
+
body["stream_options"] = { include_usage: true };
|
|
691
|
+
}
|
|
619
692
|
let res;
|
|
620
693
|
try {
|
|
621
694
|
res = await this.#fetch(`${baseUrl}/chat/completions`, {
|
|
@@ -665,9 +738,17 @@ var OpenAiCompatibleClient = class {
|
|
|
665
738
|
};
|
|
666
739
|
});
|
|
667
740
|
const content = choice["content"];
|
|
741
|
+
const thinking = choice["reasoning_content"];
|
|
742
|
+
const usageRaw = data.usage;
|
|
743
|
+
const usage = usageRaw && (typeof usageRaw["prompt_tokens"] === "number" || typeof usageRaw["completion_tokens"] === "number") ? {
|
|
744
|
+
inputTokens: typeof usageRaw["prompt_tokens"] === "number" ? usageRaw["prompt_tokens"] : 0,
|
|
745
|
+
outputTokens: typeof usageRaw["completion_tokens"] === "number" ? usageRaw["completion_tokens"] : 0
|
|
746
|
+
} : void 0;
|
|
668
747
|
return {
|
|
669
748
|
content: typeof content === "string" && content !== "" ? textParts(content) : void 0,
|
|
670
749
|
toolCalls: toolCalls?.length ? toolCalls : void 0,
|
|
750
|
+
...typeof thinking === "string" && thinking !== "" ? { thinking } : {},
|
|
751
|
+
...usage ? { usage } : {},
|
|
671
752
|
raw: data
|
|
672
753
|
};
|
|
673
754
|
} catch (e) {
|
|
@@ -756,7 +837,7 @@ function toAnthropicMessages(messages) {
|
|
|
756
837
|
const toAnthropicTools = (tools) => tools.map((tool) => ({
|
|
757
838
|
name: tool.name,
|
|
758
839
|
...tool.description ? { description: tool.description } : {},
|
|
759
|
-
input_schema: sanitizeForVendor(toPortableToolSchema(tool.inputSchema), ANTHROPIC_SCHEMA_PROFILE, tool.name)
|
|
840
|
+
input_schema: sanitizeForVendor(toPortableToolSchema(stripModelUnfillableParams(tool.inputSchema)), ANTHROPIC_SCHEMA_PROFILE, tool.name)
|
|
760
841
|
}));
|
|
761
842
|
/** Anthropic Messages API 客户端(零依赖 fetch;Node/浏览器通用) */
|
|
762
843
|
var AnthropicClient = class {
|
|
@@ -795,6 +876,8 @@ var AnthropicClient = class {
|
|
|
795
876
|
const decoder = new TextDecoder();
|
|
796
877
|
const reader = res.body.getReader();
|
|
797
878
|
const frames = createSseFrameReader();
|
|
879
|
+
let inputTokens;
|
|
880
|
+
let outputTokens;
|
|
798
881
|
const handleFrame = function* (data) {
|
|
799
882
|
let chunk;
|
|
800
883
|
try {
|
|
@@ -804,6 +887,17 @@ var AnthropicClient = class {
|
|
|
804
887
|
}
|
|
805
888
|
const type = chunk["type"];
|
|
806
889
|
if (type === "error") throw new WebSkillError("LLM_REQUEST_FAILED", `LLM stream error: ${chunk["error"]?.message ?? data}`);
|
|
890
|
+
if (type === "message_start") {
|
|
891
|
+
const usage = chunk["message"]?.["usage"];
|
|
892
|
+
if (typeof usage?.["input_tokens"] === "number") inputTokens = usage["input_tokens"];
|
|
893
|
+
if (typeof usage?.["output_tokens"] === "number") outputTokens = usage["output_tokens"];
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
if (type === "message_delta") {
|
|
897
|
+
const usage = chunk["usage"];
|
|
898
|
+
if (typeof usage?.["output_tokens"] === "number") outputTokens = usage["output_tokens"];
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
807
901
|
if (type === "content_block_start") {
|
|
808
902
|
const block = chunk["content_block"];
|
|
809
903
|
if (block?.["type"] === "tool_use") {
|
|
@@ -826,6 +920,13 @@ var AnthropicClient = class {
|
|
|
826
920
|
};
|
|
827
921
|
return;
|
|
828
922
|
}
|
|
923
|
+
if (delta?.["type"] === "thinking_delta" && typeof delta["thinking"] === "string" && delta["thinking"] !== "") {
|
|
924
|
+
yield {
|
|
925
|
+
type: "thinking-delta",
|
|
926
|
+
delta: delta["thinking"]
|
|
927
|
+
};
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
829
930
|
if (delta?.["type"] === "input_json_delta" && typeof delta["partial_json"] === "string") {
|
|
830
931
|
const acc = toolCallsByIndex.get(index) ?? {
|
|
831
932
|
id: "",
|
|
@@ -866,7 +967,14 @@ var AnthropicClient = class {
|
|
|
866
967
|
};
|
|
867
968
|
})
|
|
868
969
|
};
|
|
869
|
-
|
|
970
|
+
const usage = inputTokens !== void 0 || outputTokens !== void 0 ? {
|
|
971
|
+
inputTokens: inputTokens ?? 0,
|
|
972
|
+
outputTokens: outputTokens ?? 0
|
|
973
|
+
} : void 0;
|
|
974
|
+
yield {
|
|
975
|
+
type: "done",
|
|
976
|
+
...usage ? { usage } : {}
|
|
977
|
+
};
|
|
870
978
|
}
|
|
871
979
|
async #post(input, stream) {
|
|
872
980
|
validateLlmMessages(input.messages);
|
|
@@ -878,7 +986,12 @@ var AnthropicClient = class {
|
|
|
878
986
|
};
|
|
879
987
|
if (system !== void 0) body["system"] = system;
|
|
880
988
|
if (input.tools?.length) body["tools"] = toAnthropicTools(input.tools);
|
|
881
|
-
|
|
989
|
+
const thinkingBudget = this.#config.thinkingBudgetTokens;
|
|
990
|
+
if (thinkingBudget !== void 0 && Number.isInteger(thinkingBudget) && thinkingBudget > 0) body["thinking"] = {
|
|
991
|
+
type: "enabled",
|
|
992
|
+
budget_tokens: thinkingBudget
|
|
993
|
+
};
|
|
994
|
+
else if (input.temperature !== void 0) body["temperature"] = input.temperature;
|
|
882
995
|
if (stream) body["stream"] = true;
|
|
883
996
|
let res;
|
|
884
997
|
try {
|
|
@@ -897,10 +1010,26 @@ var AnthropicClient = class {
|
|
|
897
1010
|
}
|
|
898
1011
|
return res;
|
|
899
1012
|
}
|
|
900
|
-
/**
|
|
1013
|
+
/**
|
|
1014
|
+
* 真实调用路径探测(FR-27.1):`POST /v1/messages` 最小请求(max_tokens=1),200 即可达。
|
|
1015
|
+
* Anthropic 兼容端点(如 LM Studio 的 /anthropic 层)不实现 `GET /v1/models`,
|
|
1016
|
+
* 用 /models 探测会把「能对话」误判为「不可达」——checkAvailability 的语义从
|
|
1017
|
+
* 「/models 可达」改为「能完成一次真实调用」。
|
|
1018
|
+
*/
|
|
901
1019
|
async checkAvailability() {
|
|
902
1020
|
try {
|
|
903
|
-
return (await this.#fetch(`${this.#baseUrl()}/v1/
|
|
1021
|
+
return (await this.#fetch(`${this.#baseUrl()}/v1/messages`, {
|
|
1022
|
+
method: "POST",
|
|
1023
|
+
headers: this.#headers(),
|
|
1024
|
+
body: JSON.stringify({
|
|
1025
|
+
model: this.#config.model,
|
|
1026
|
+
max_tokens: 1,
|
|
1027
|
+
messages: [{
|
|
1028
|
+
role: "user",
|
|
1029
|
+
content: "ping"
|
|
1030
|
+
}]
|
|
1031
|
+
})
|
|
1032
|
+
})).ok;
|
|
904
1033
|
} catch {
|
|
905
1034
|
return false;
|
|
906
1035
|
}
|
|
@@ -910,14 +1039,22 @@ var AnthropicClient = class {
|
|
|
910
1039
|
const blocks = data.content;
|
|
911
1040
|
if (!Array.isArray(blocks)) throw new Error("response has no content blocks");
|
|
912
1041
|
const text = blocks.filter((b) => b["type"] === "text").map((b) => String(b["text"] ?? "")).join("");
|
|
1042
|
+
const thinking = blocks.filter((b) => b["type"] === "thinking").map((b) => String(b["thinking"] ?? "")).join("");
|
|
913
1043
|
const toolCalls = blocks.filter((b) => b["type"] === "tool_use").map((b) => ({
|
|
914
1044
|
id: typeof b["id"] === "string" ? b["id"] : "",
|
|
915
1045
|
name: typeof b["name"] === "string" ? b["name"] : "",
|
|
916
1046
|
arguments: typeof b["input"] === "object" && b["input"] !== null ? b["input"] : {}
|
|
917
1047
|
}));
|
|
1048
|
+
const usageRaw = data.usage;
|
|
1049
|
+
const usage = typeof usageRaw?.["input_tokens"] === "number" || typeof usageRaw?.["output_tokens"] === "number" ? {
|
|
1050
|
+
inputTokens: typeof usageRaw["input_tokens"] === "number" ? usageRaw["input_tokens"] : 0,
|
|
1051
|
+
outputTokens: typeof usageRaw["output_tokens"] === "number" ? usageRaw["output_tokens"] : 0
|
|
1052
|
+
} : void 0;
|
|
918
1053
|
return {
|
|
919
1054
|
content: text === "" ? void 0 : textParts(text),
|
|
920
1055
|
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
1056
|
+
...thinking === "" ? {} : { thinking },
|
|
1057
|
+
...usage ? { usage } : {},
|
|
921
1058
|
raw: data
|
|
922
1059
|
};
|
|
923
1060
|
} catch (e) {
|
|
@@ -926,6 +1063,18 @@ var AnthropicClient = class {
|
|
|
926
1063
|
}
|
|
927
1064
|
};
|
|
928
1065
|
const errorMessage = (e) => e instanceof Error ? e.message : String(e);
|
|
1066
|
+
/** usageMetadata → LlmTokenUsage(promptTokenCount / candidatesTokenCount;无字段返回 undefined) */
|
|
1067
|
+
function parseGenAiUsage(data) {
|
|
1068
|
+
const usage = data?.usageMetadata;
|
|
1069
|
+
if (!usage) return void 0;
|
|
1070
|
+
const input = usage["promptTokenCount"];
|
|
1071
|
+
const output = usage["candidatesTokenCount"];
|
|
1072
|
+
if (typeof input !== "number" && typeof output !== "number") return void 0;
|
|
1073
|
+
return {
|
|
1074
|
+
inputTokens: typeof input === "number" ? input : 0,
|
|
1075
|
+
outputTokens: typeof output === "number" ? output : 0
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
929
1078
|
/** parts → GenAI parts(二进制统一走 inlineData) */
|
|
930
1079
|
const toGenAiParts = (parts, where) => parts.map((part) => {
|
|
931
1080
|
switch (part.type) {
|
|
@@ -999,7 +1148,7 @@ function toGenAiContents(messages) {
|
|
|
999
1148
|
const toGenAiTools = (tools) => [{ functionDeclarations: tools.map((tool) => ({
|
|
1000
1149
|
name: tool.name,
|
|
1001
1150
|
...tool.description ? { description: tool.description } : {},
|
|
1002
|
-
parameters: sanitizeForVendor(toPortableToolSchema(tool.inputSchema), GOOGLE_SCHEMA_PROFILE, tool.name)
|
|
1151
|
+
parameters: sanitizeForVendor(toPortableToolSchema(stripModelUnfillableParams(tool.inputSchema)), GOOGLE_SCHEMA_PROFILE, tool.name)
|
|
1003
1152
|
})) }];
|
|
1004
1153
|
/** Google GenAI(generateContent / streamGenerateContent)客户端(零依赖 fetch) */
|
|
1005
1154
|
var GoogleGenAiClient = class {
|
|
@@ -1023,9 +1172,11 @@ var GoogleGenAiClient = class {
|
|
|
1023
1172
|
const parts = this.#responseParts(data);
|
|
1024
1173
|
const text = parts.filter((p) => typeof p["text"] === "string").map((p) => String(p["text"])).join("");
|
|
1025
1174
|
const toolCalls = this.#functionCalls(parts);
|
|
1175
|
+
const usage = parseGenAiUsage(data);
|
|
1026
1176
|
return {
|
|
1027
1177
|
content: text === "" ? void 0 : textParts(text),
|
|
1028
1178
|
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
1179
|
+
...usage ? { usage } : {},
|
|
1029
1180
|
raw: data
|
|
1030
1181
|
};
|
|
1031
1182
|
}
|
|
@@ -1037,6 +1188,7 @@ var GoogleGenAiClient = class {
|
|
|
1037
1188
|
const decoder = new TextDecoder();
|
|
1038
1189
|
const reader = res.body.getReader();
|
|
1039
1190
|
const frames = createSseFrameReader();
|
|
1191
|
+
let usage;
|
|
1040
1192
|
const handleFrame = function* (data) {
|
|
1041
1193
|
let chunk;
|
|
1042
1194
|
try {
|
|
@@ -1044,6 +1196,7 @@ var GoogleGenAiClient = class {
|
|
|
1044
1196
|
} catch (e) {
|
|
1045
1197
|
throw new WebSkillError("LLM_REQUEST_FAILED", `Failed to parse LLM stream frame: ${errorMessage(e)}`, data);
|
|
1046
1198
|
}
|
|
1199
|
+
usage = parseGenAiUsage(chunk) ?? usage;
|
|
1047
1200
|
const parts = chunk.candidates?.[0]?.content?.parts;
|
|
1048
1201
|
for (const part of parts ?? []) {
|
|
1049
1202
|
if (typeof part["text"] === "string" && part["text"] !== "") {
|
|
@@ -1075,7 +1228,10 @@ var GoogleGenAiClient = class {
|
|
|
1075
1228
|
type: "tool-calls",
|
|
1076
1229
|
toolCalls
|
|
1077
1230
|
};
|
|
1078
|
-
yield {
|
|
1231
|
+
yield {
|
|
1232
|
+
type: "done",
|
|
1233
|
+
...usage ? { usage } : {}
|
|
1234
|
+
};
|
|
1079
1235
|
}
|
|
1080
1236
|
async #post(input, stream) {
|
|
1081
1237
|
const model = input.model ?? this.#config.model;
|
|
@@ -1136,6 +1292,17 @@ var GoogleGenAiClient = class {
|
|
|
1136
1292
|
return out;
|
|
1137
1293
|
}
|
|
1138
1294
|
};
|
|
1295
|
+
/** v5 { inputTokens, outputTokens } 与 v4 { promptTokens, completionTokens } 两种形状都接 */
|
|
1296
|
+
function fromVercelUsage(raw) {
|
|
1297
|
+
const u = raw ?? {};
|
|
1298
|
+
const input = u["inputTokens"] ?? u["promptTokens"];
|
|
1299
|
+
const output = u["outputTokens"] ?? u["completionTokens"];
|
|
1300
|
+
if (typeof input !== "number" && typeof output !== "number") return void 0;
|
|
1301
|
+
return {
|
|
1302
|
+
inputTokens: typeof input === "number" ? input : 0,
|
|
1303
|
+
outputTokens: typeof output === "number" ? output : 0
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1139
1306
|
function toVercelToolSpecs(tools) {
|
|
1140
1307
|
const out = {};
|
|
1141
1308
|
for (const tool of tools) out[tool.name] = {
|
|
@@ -1144,6 +1311,16 @@ function toVercelToolSpecs(tools) {
|
|
|
1144
1311
|
};
|
|
1145
1312
|
return out;
|
|
1146
1313
|
}
|
|
1314
|
+
/**
|
|
1315
|
+
* 归一化供应商思考内容:v5 的 `reasoningText`(字符串)与 v4 的 `reasoning`
|
|
1316
|
+
* (Array<{ text; type }>)都能落到同一条 `LlmResponse.thinking`。
|
|
1317
|
+
*/
|
|
1318
|
+
function normalizeThinking(value) {
|
|
1319
|
+
if (typeof value === "string") return value !== "" ? value : void 0;
|
|
1320
|
+
if (!Array.isArray(value)) return void 0;
|
|
1321
|
+
const text = value.map((part) => typeof part?.text === "string" ? part.text : "").join("");
|
|
1322
|
+
return text !== "" ? text : void 0;
|
|
1323
|
+
}
|
|
1147
1324
|
/** 接受 generateText 的返回值形状:{ text?, toolCalls?: [{ toolCallId, toolName, input|args }] } */
|
|
1148
1325
|
function fromVercelResult(result) {
|
|
1149
1326
|
const r = result;
|
|
@@ -1155,9 +1332,13 @@ function fromVercelResult(result) {
|
|
|
1155
1332
|
arguments: typeof args === "object" && args !== null ? args : {}
|
|
1156
1333
|
};
|
|
1157
1334
|
});
|
|
1335
|
+
const thinking = normalizeThinking(r.reasoningText ?? r.reasoning);
|
|
1336
|
+
const usage = fromVercelUsage(r.usage);
|
|
1158
1337
|
return {
|
|
1159
1338
|
content: typeof r.text === "string" && r.text !== "" ? textParts(r.text) : void 0,
|
|
1160
1339
|
toolCalls: toolCalls.length ? toolCalls : void 0,
|
|
1340
|
+
...thinking !== void 0 ? { thinking } : {},
|
|
1341
|
+
...usage ? { usage } : {},
|
|
1161
1342
|
raw: result
|
|
1162
1343
|
};
|
|
1163
1344
|
}
|
|
@@ -1175,6 +1356,13 @@ function fromVercelStreamPart(part) {
|
|
|
1175
1356
|
delta
|
|
1176
1357
|
} : void 0;
|
|
1177
1358
|
}
|
|
1359
|
+
case "reasoning-delta": {
|
|
1360
|
+
const delta = p["text"] ?? p["delta"];
|
|
1361
|
+
return typeof delta === "string" && delta !== "" ? {
|
|
1362
|
+
type: "thinking-delta",
|
|
1363
|
+
delta
|
|
1364
|
+
} : void 0;
|
|
1365
|
+
}
|
|
1178
1366
|
case "tool-call": return {
|
|
1179
1367
|
type: "tool-calls",
|
|
1180
1368
|
toolCalls: [{
|
|
@@ -1183,7 +1371,13 @@ function fromVercelStreamPart(part) {
|
|
|
1183
1371
|
arguments: typeof p["input"] === "object" && p["input"] !== null ? p["input"] : {}
|
|
1184
1372
|
}]
|
|
1185
1373
|
};
|
|
1186
|
-
case "finish":
|
|
1374
|
+
case "finish": {
|
|
1375
|
+
const usage = fromVercelUsage(p["totalUsage"] ?? p["usage"]);
|
|
1376
|
+
return {
|
|
1377
|
+
type: "done",
|
|
1378
|
+
...usage ? { usage } : {}
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1187
1381
|
default: return;
|
|
1188
1382
|
}
|
|
1189
1383
|
}
|
|
@@ -1421,6 +1615,16 @@ const ASK_USER_TOOL = {
|
|
|
1421
1615
|
source: "builtin"
|
|
1422
1616
|
};
|
|
1423
1617
|
/**
|
|
1618
|
+
* 越权 / 绝对路径 / 父级遍历的引用读取:策略拒绝(FS_PERMISSION_DENIED,
|
|
1619
|
+
* 计入 POLICY_DENIAL_CODES → 不计入隔离失败计数),
|
|
1620
|
+
* 与「references/ 内路径确实不存在 → FS_NOT_FOUND」区分(0.9.0 分册 14,UX-04)。
|
|
1621
|
+
*/
|
|
1622
|
+
function assertReferencePath(relativePath) {
|
|
1623
|
+
const trimmed = relativePath.trim();
|
|
1624
|
+
if (trimmed === "" || trimmed.startsWith("/") || trimmed.startsWith("\\") || /^[a-zA-Z]:[\\/]/.test(trimmed)) throw new WebSkillError("FS_PERMISSION_DENIED", `Reference access denied: absolute or empty path (path: ${JSON.stringify(relativePath)})`);
|
|
1625
|
+
if (trimmed.replace(/\\/g, "/").split("/").includes("..")) throw new WebSkillError("FS_PERMISSION_DENIED", `Reference access denied: parent traversal (path: ${JSON.stringify(relativePath)})`);
|
|
1626
|
+
}
|
|
1627
|
+
/**
|
|
1424
1628
|
* 脚本执行上下文:只暴露 readReference / writeArtifact 两个显式能力,
|
|
1425
1629
|
* 不暴露 fs 本体(沙箱语义,对齐 deferred-items D1)。
|
|
1426
1630
|
*/
|
|
@@ -1431,6 +1635,7 @@ function createScriptContext(deps) {
|
|
|
1431
1635
|
skillName,
|
|
1432
1636
|
runId,
|
|
1433
1637
|
async readReference(relativePath) {
|
|
1638
|
+
assertReferencePath(relativePath);
|
|
1434
1639
|
return readFs.readText(resolveInsideRoot(skillRoot, `references/${relativePath}`));
|
|
1435
1640
|
},
|
|
1436
1641
|
async writeArtifact(path, content, options) {
|
|
@@ -1776,7 +1981,7 @@ const DEFAULT_USER_PROFILE_LIMITS = {
|
|
|
1776
1981
|
recordLimit: 500,
|
|
1777
1982
|
valueMaxChars: 2e3,
|
|
1778
1983
|
sampleLimit: 80,
|
|
1779
|
-
injectMaxBytes:
|
|
1984
|
+
injectMaxBytes: 4e3,
|
|
1780
1985
|
entryLimit: 40
|
|
1781
1986
|
};
|
|
1782
1987
|
/** @experimental */
|
|
@@ -2026,8 +2231,8 @@ function extractSkillCandidate(data) {
|
|
|
2026
2231
|
*/
|
|
2027
2232
|
const DEFAULT_LOOP_LIMITS = {
|
|
2028
2233
|
maxTurns: 10,
|
|
2029
|
-
totalTimeoutMs:
|
|
2030
|
-
toolTimeoutMs:
|
|
2234
|
+
totalTimeoutMs: 36e5,
|
|
2235
|
+
toolTimeoutMs: 6e5
|
|
2031
2236
|
};
|
|
2032
2237
|
const RUN_SNAPSHOT_SCHEMA_VERSION = 3;
|
|
2033
2238
|
/** @experimental */
|
|
@@ -2055,7 +2260,7 @@ var FsRunSnapshotStore = class {
|
|
|
2055
2260
|
return resolveInsideRoot(this.#root, `${runId}${SNAPSHOT_SUFFIX}`);
|
|
2056
2261
|
}
|
|
2057
2262
|
async save(snapshot) {
|
|
2058
|
-
await this.#fs
|
|
2263
|
+
await atomicWriteText(this.#fs, this.#path(snapshot.runId), JSON.stringify(snapshot, null, 2));
|
|
2059
2264
|
await this.#pruneExpired();
|
|
2060
2265
|
}
|
|
2061
2266
|
async load(runId) {
|
|
@@ -2267,7 +2472,7 @@ var AgentLoop = class {
|
|
|
2267
2472
|
maxTurns: config.maxTurns ?? DEFAULT_LOOP_LIMITS.maxTurns,
|
|
2268
2473
|
totalTimeoutMs: config.totalTimeoutMs ?? DEFAULT_LOOP_LIMITS.totalTimeoutMs,
|
|
2269
2474
|
toolTimeoutMs: config.toolTimeoutMs ?? DEFAULT_LOOP_LIMITS.toolTimeoutMs,
|
|
2270
|
-
toolResultMaxBytes: config.toolResultMaxBytes ??
|
|
2475
|
+
toolResultMaxBytes: config.toolResultMaxBytes ?? 256e3,
|
|
2271
2476
|
paramHistoryLimit: config.paramHistoryLimit ?? 50,
|
|
2272
2477
|
toolCallingDisabled: config.toolCallingDisabled ?? false,
|
|
2273
2478
|
maxUnknownToolRetries: config.maxUnknownToolRetries ?? 2,
|
|
@@ -2330,6 +2535,7 @@ var AgentLoop = class {
|
|
|
2330
2535
|
surfacePatchCount: 0,
|
|
2331
2536
|
processedSurfaceActionNonces: /* @__PURE__ */ new Set(),
|
|
2332
2537
|
emittedToolEvents: /* @__PURE__ */ new Set(),
|
|
2538
|
+
artifactPaths: /* @__PURE__ */ new Set(),
|
|
2333
2539
|
unknownToolCalls: 0,
|
|
2334
2540
|
startMs,
|
|
2335
2541
|
pausedMs: 0,
|
|
@@ -2480,6 +2686,23 @@ var AgentLoop = class {
|
|
|
2480
2686
|
const code = e instanceof WebSkillError && (e.code === "LLM_UNAVAILABLE" || e.code === "LLM_REQUEST_FAILED") ? e.code : "LLM_REQUEST_FAILED";
|
|
2481
2687
|
return finish("failed", "llm-error", messageOf(e), code);
|
|
2482
2688
|
}
|
|
2689
|
+
if (response.thinking !== void 0 && response.thinking !== "") {
|
|
2690
|
+
state.trace.record("llm.thinking", { data: {
|
|
2691
|
+
turn,
|
|
2692
|
+
text: response.thinking
|
|
2693
|
+
} });
|
|
2694
|
+
const bus = this.#deps.eventBus;
|
|
2695
|
+
if (!llm.stream && bus && bus.listenerCount() > 0) bus.emit({
|
|
2696
|
+
phase: "execute",
|
|
2697
|
+
runId: state.runId,
|
|
2698
|
+
sessionId: state.run.sessionId,
|
|
2699
|
+
ts: state.now(),
|
|
2700
|
+
data: {
|
|
2701
|
+
kind: "thinking-delta",
|
|
2702
|
+
delta: response.thinking
|
|
2703
|
+
}
|
|
2704
|
+
});
|
|
2705
|
+
}
|
|
2483
2706
|
const responseText = partsToText(response.content);
|
|
2484
2707
|
if (!llm.stream && responseText !== "") await this.#deps.uiBridge?.onTextDelta?.(state.runId, responseText);
|
|
2485
2708
|
trace.record("llm.response", { data: {
|
|
@@ -2487,6 +2710,11 @@ var AgentLoop = class {
|
|
|
2487
2710
|
hasToolCalls: Boolean(response.toolCalls?.length),
|
|
2488
2711
|
contentLength: responseText.length
|
|
2489
2712
|
} });
|
|
2713
|
+
if (response.usage) trace.record("llm.usage", { data: {
|
|
2714
|
+
turn,
|
|
2715
|
+
inputTokens: response.usage.inputTokens,
|
|
2716
|
+
outputTokens: response.usage.outputTokens
|
|
2717
|
+
} });
|
|
2490
2718
|
if (!response.toolCalls?.length) {
|
|
2491
2719
|
messages.push({
|
|
2492
2720
|
role: "assistant",
|
|
@@ -2696,6 +2924,7 @@ var AgentLoop = class {
|
|
|
2696
2924
|
surfacePatchCount: 0,
|
|
2697
2925
|
processedSurfaceActionNonces: new Set(snapshot.processedSurfaceActionNonces ?? []),
|
|
2698
2926
|
emittedToolEvents: /* @__PURE__ */ new Set(),
|
|
2927
|
+
artifactPaths: /* @__PURE__ */ new Set(),
|
|
2699
2928
|
unknownToolCalls: 0,
|
|
2700
2929
|
startMs,
|
|
2701
2930
|
pausedMs: snapshot.pausedMs ?? 0,
|
|
@@ -2816,6 +3045,7 @@ var AgentLoop = class {
|
|
|
2816
3045
|
/**
|
|
2817
3046
|
* 流式 LLM 调用:text-delta 仅在 EventBus 有订阅者时发射 llm.delta 事件,
|
|
2818
3047
|
* 并同步转发 uiBridge.onTextDelta;tool-calls/done 组装为 LlmResponse 走原有分支。
|
|
3048
|
+
* thinking-delta 同理累积成 `response.thinking`(每轮一段,UI-UX5 #32)。
|
|
2819
3049
|
*/
|
|
2820
3050
|
async #completeStreaming(llm, state, input) {
|
|
2821
3051
|
const emitDelta = (delta) => {
|
|
@@ -2832,18 +3062,41 @@ var AgentLoop = class {
|
|
|
2832
3062
|
});
|
|
2833
3063
|
this.#deps.uiBridge?.onTextDelta?.(state.runId, delta);
|
|
2834
3064
|
};
|
|
3065
|
+
const emitThinkingDelta = (delta) => {
|
|
3066
|
+
const bus = this.#deps.eventBus;
|
|
3067
|
+
if (bus && bus.listenerCount() > 0) bus.emit({
|
|
3068
|
+
phase: "execute",
|
|
3069
|
+
runId: state.runId,
|
|
3070
|
+
sessionId: state.run.sessionId,
|
|
3071
|
+
ts: state.now(),
|
|
3072
|
+
data: {
|
|
3073
|
+
kind: "thinking-delta",
|
|
3074
|
+
delta
|
|
3075
|
+
}
|
|
3076
|
+
});
|
|
3077
|
+
};
|
|
2835
3078
|
let content = "";
|
|
3079
|
+
let thinking = "";
|
|
2836
3080
|
let doneContent;
|
|
3081
|
+
let usage;
|
|
2837
3082
|
const toolCalls = [];
|
|
2838
3083
|
for await (const event of llm.stream(input)) if (event.type === "text-delta") {
|
|
2839
3084
|
content += event.delta;
|
|
2840
3085
|
emitDelta(event.delta);
|
|
3086
|
+
} else if (event.type === "thinking-delta") {
|
|
3087
|
+
thinking += event.delta;
|
|
3088
|
+
emitThinkingDelta(event.delta);
|
|
2841
3089
|
} else if (event.type === "tool-calls") toolCalls.push(...event.toolCalls);
|
|
2842
|
-
else if (event.type === "done")
|
|
3090
|
+
else if (event.type === "done") {
|
|
3091
|
+
doneContent = event.content;
|
|
3092
|
+
usage = event.usage ?? usage;
|
|
3093
|
+
}
|
|
2843
3094
|
const text = doneContent ?? content;
|
|
2844
3095
|
return {
|
|
2845
3096
|
content: text === "" ? void 0 : textParts(text),
|
|
2846
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : void 0
|
|
3097
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
3098
|
+
...thinking === "" ? {} : { thinking },
|
|
3099
|
+
...usage ? { usage } : {}
|
|
2847
3100
|
};
|
|
2848
3101
|
}
|
|
2849
3102
|
/** 生命周期接线:更新 phase、发事件、跑钩子 */
|
|
@@ -3070,10 +3323,13 @@ var AgentLoop = class {
|
|
|
3070
3323
|
});
|
|
3071
3324
|
this.#emitTool(state, "failed", call, result.error?.code);
|
|
3072
3325
|
}
|
|
3073
|
-
for (const artifact of result.artifacts ?? [])
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3326
|
+
for (const artifact of result.artifacts ?? []) {
|
|
3327
|
+
state.artifactPaths.add(artifact.path);
|
|
3328
|
+
state.trace.record("artifact.created", { data: {
|
|
3329
|
+
artifactId: artifact.id,
|
|
3330
|
+
path: artifact.path
|
|
3331
|
+
} });
|
|
3332
|
+
}
|
|
3077
3333
|
if (state.unknownToolCalls > this.#config.maxUnknownToolRetries) throw new RunTerminated({
|
|
3078
3334
|
status: "failed",
|
|
3079
3335
|
reason: "tool-resolution-exhausted",
|
|
@@ -3089,7 +3345,12 @@ var AgentLoop = class {
|
|
|
3089
3345
|
state.trace.record("run.warning", { message: "UiBridge does not support renderSurface; UI surface was not rendered" });
|
|
3090
3346
|
return;
|
|
3091
3347
|
}
|
|
3092
|
-
|
|
3348
|
+
let attributed = this.#attributeSurfaceEvent(state, event);
|
|
3349
|
+
if (attributed.type === "open") {
|
|
3350
|
+
const kept = await this.#stripDuplicateFileLinks(state, attributed);
|
|
3351
|
+
if (kept === void 0) return;
|
|
3352
|
+
attributed = kept;
|
|
3353
|
+
}
|
|
3093
3354
|
if (attributed.type === "patch") this.#consumeSurfacePatchBudget(state);
|
|
3094
3355
|
await bridge.renderSurface(attributed);
|
|
3095
3356
|
state.surfaceEvents.push(structuredClone(attributed));
|
|
@@ -3132,6 +3393,44 @@ var AgentLoop = class {
|
|
|
3132
3393
|
}))
|
|
3133
3394
|
};
|
|
3134
3395
|
}
|
|
3396
|
+
/**
|
|
3397
|
+
* 剥掉指向「本 run 已写出 artifact」的 FileLink 节点(UI-UX5 #5):
|
|
3398
|
+
* 宿主在 run 完成时会把每个 artifact 自动渲染成下载卡(`buildRenderResult`),
|
|
3399
|
+
* 模型/技能再建一张就是重复。整棵树剥空返回 undefined(放弃这次 open);
|
|
3400
|
+
* 有剥离就记一条 run.warning,让「少了一张卡」在轨迹里可查。
|
|
3401
|
+
*/
|
|
3402
|
+
async #stripDuplicateFileLinks(state, event) {
|
|
3403
|
+
if (state.artifactPaths.size === 0) {
|
|
3404
|
+
const listed = await this.#deps.artifactStore.listArtifacts(state.runId).catch(() => []);
|
|
3405
|
+
for (const artifact of listed) state.artifactPaths.add(artifact.path);
|
|
3406
|
+
}
|
|
3407
|
+
if (state.artifactPaths.size === 0) return event;
|
|
3408
|
+
const paths = state.artifactPaths;
|
|
3409
|
+
let stripped = 0;
|
|
3410
|
+
const walk = (node) => {
|
|
3411
|
+
if (node.component === "FileLink") {
|
|
3412
|
+
const path = node.props?.["path"];
|
|
3413
|
+
if (typeof path === "string" && paths.has(path)) {
|
|
3414
|
+
stripped += 1;
|
|
3415
|
+
return;
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
if (!node.children || node.children.length === 0) return node;
|
|
3419
|
+
const children = node.children.map(walk).filter((child) => child !== void 0);
|
|
3420
|
+
if (children.length === node.children.length) return node;
|
|
3421
|
+
return {
|
|
3422
|
+
...node,
|
|
3423
|
+
children
|
|
3424
|
+
};
|
|
3425
|
+
};
|
|
3426
|
+
const node = walk(event.node);
|
|
3427
|
+
if (stripped === 0) return event;
|
|
3428
|
+
state.trace.record("run.warning", { message: `Dropped ${stripped} FileLink node(s) duplicating auto-rendered artifact download card(s)` });
|
|
3429
|
+
return node === void 0 ? void 0 : {
|
|
3430
|
+
...event,
|
|
3431
|
+
node
|
|
3432
|
+
};
|
|
3433
|
+
}
|
|
3135
3434
|
/** Awaits the single action emitted with the most recently persisted tool result. */
|
|
3136
3435
|
async #drainSurfaceAction(state) {
|
|
3137
3436
|
const request = state.pendingSurfaceAction;
|
|
@@ -3410,7 +3709,7 @@ var AgentLoop = class {
|
|
|
3410
3709
|
}))];
|
|
3411
3710
|
}
|
|
3412
3711
|
/**
|
|
3413
|
-
* 工具结果回喂序列化:超过 toolResultMaxBytes(默认
|
|
3712
|
+
* 工具结果回喂序列化:超过 toolResultMaxBytes(默认 256KB)时头尾保留截断,
|
|
3414
3713
|
* 完整内容经 artifactStore 落 artifact,回喂摘要含 artifact id。
|
|
3415
3714
|
*/
|
|
3416
3715
|
async #serializeToolResult(call, result, state) {
|
|
@@ -4398,7 +4697,7 @@ var FsMemoryStore = class {
|
|
|
4398
4697
|
}
|
|
4399
4698
|
}
|
|
4400
4699
|
async set(scope, key, value) {
|
|
4401
|
-
await this.#fs
|
|
4700
|
+
await atomicWriteText(this.#fs, this.#keyPath(scope, key), JSON.stringify(value, null, 2));
|
|
4402
4701
|
}
|
|
4403
4702
|
async delete(scope, key) {
|
|
4404
4703
|
const path = this.#keyPath(scope, key);
|
|
@@ -4634,7 +4933,7 @@ var FsArtifactStore = class {
|
|
|
4634
4933
|
}
|
|
4635
4934
|
async createTextArtifact(input) {
|
|
4636
4935
|
const size = new TextEncoder().encode(input.content).length;
|
|
4637
|
-
await this.#fs
|
|
4936
|
+
await atomicWriteText(this.#fs, this.#artifactPath(input.runId, input.path), input.content);
|
|
4638
4937
|
return this.#persist({
|
|
4639
4938
|
...input,
|
|
4640
4939
|
type: "text",
|
|
@@ -4684,7 +4983,7 @@ var FsArtifactStore = class {
|
|
|
4684
4983
|
metadata: input.metadata
|
|
4685
4984
|
};
|
|
4686
4985
|
const next = [...(await this.listArtifacts(input.runId)).filter((a) => a.id !== artifact.id), artifact];
|
|
4687
|
-
await this.#fs
|
|
4986
|
+
await atomicWriteText(this.#fs, `${this.#root}/${input.runId}/${INDEX_FILE$1}`, JSON.stringify({ artifacts: next }, null, 2));
|
|
4688
4987
|
return artifact;
|
|
4689
4988
|
}
|
|
4690
4989
|
};
|
|
@@ -4903,6 +5202,38 @@ var CapabilityApproval = class CapabilityApproval {
|
|
|
4903
5202
|
return "allowed";
|
|
4904
5203
|
}
|
|
4905
5204
|
};
|
|
5205
|
+
/**
|
|
5206
|
+
* 从 run 的 trace 聚合大模型用量(请求次数 + 输入/输出 token)。
|
|
5207
|
+
* 与 `summarizeToolCalls` 同型:消费方不再各自遍历 trace。
|
|
5208
|
+
* @stable
|
|
5209
|
+
*/
|
|
5210
|
+
function summarizeRunUsage(trace) {
|
|
5211
|
+
let llmCalls = 0;
|
|
5212
|
+
let inputTokens = 0;
|
|
5213
|
+
let outputTokens = 0;
|
|
5214
|
+
let hasUsage = false;
|
|
5215
|
+
for (const event of trace) {
|
|
5216
|
+
if (event.type === "llm.request") {
|
|
5217
|
+
llmCalls += 1;
|
|
5218
|
+
continue;
|
|
5219
|
+
}
|
|
5220
|
+
if (event.type !== "llm.usage") continue;
|
|
5221
|
+
const input = event.data?.["inputTokens"];
|
|
5222
|
+
const output = event.data?.["outputTokens"];
|
|
5223
|
+
const hasInput = typeof input === "number" && Number.isFinite(input);
|
|
5224
|
+
const hasOutput = typeof output === "number" && Number.isFinite(output);
|
|
5225
|
+
if (hasInput) inputTokens += input;
|
|
5226
|
+
if (hasOutput) outputTokens += output;
|
|
5227
|
+
if (hasInput || hasOutput) hasUsage = true;
|
|
5228
|
+
}
|
|
5229
|
+
return {
|
|
5230
|
+
llmCalls,
|
|
5231
|
+
...hasUsage ? {
|
|
5232
|
+
inputTokens,
|
|
5233
|
+
outputTokens
|
|
5234
|
+
} : {}
|
|
5235
|
+
};
|
|
5236
|
+
}
|
|
4906
5237
|
const RUN_TRACE_SCHEMA_VERSION = 1;
|
|
4907
5238
|
/** 终止原因:取最后一条 run.completed/cancelled/failed 事件的 data.reason */
|
|
4908
5239
|
function extractEndReason(events) {
|
|
@@ -4917,6 +5248,7 @@ function extractEndReason(events) {
|
|
|
4917
5248
|
function summarize(trace) {
|
|
4918
5249
|
const endReason = extractEndReason(trace.events);
|
|
4919
5250
|
const durationMs = trace.endedAt === void 0 ? void 0 : Date.parse(trace.endedAt) - Date.parse(trace.startedAt);
|
|
5251
|
+
const runUsage = summarizeRunUsage(trace.events);
|
|
4920
5252
|
return {
|
|
4921
5253
|
runId: trace.runId,
|
|
4922
5254
|
startedAt: trace.startedAt,
|
|
@@ -4927,7 +5259,11 @@ function summarize(trace) {
|
|
|
4927
5259
|
...trace.sessionId !== void 0 ? { sessionId: trace.sessionId } : {},
|
|
4928
5260
|
...trace.endedAt !== void 0 ? { endedAt: trace.endedAt } : {},
|
|
4929
5261
|
...endReason !== void 0 ? { endReason } : {},
|
|
4930
|
-
...durationMs !== void 0 && Number.isFinite(durationMs) && durationMs >= 0 ? { durationMs } : {}
|
|
5262
|
+
...durationMs !== void 0 && Number.isFinite(durationMs) && durationMs >= 0 ? { durationMs } : {},
|
|
5263
|
+
...runUsage.inputTokens !== void 0 ? { usage: {
|
|
5264
|
+
inputTokens: runUsage.inputTokens,
|
|
5265
|
+
outputTokens: runUsage.outputTokens ?? 0
|
|
5266
|
+
} } : {}
|
|
4931
5267
|
};
|
|
4932
5268
|
}
|
|
4933
5269
|
function parseTraceFile(raw, path) {
|
|
@@ -4994,7 +5330,7 @@ var FsRunTraceStore = class {
|
|
|
4994
5330
|
events: run.trace
|
|
4995
5331
|
};
|
|
4996
5332
|
try {
|
|
4997
|
-
await this.#fs
|
|
5333
|
+
await atomicWriteText(this.#fs, this.#path(run.id), JSON.stringify(trace, null, 2));
|
|
4998
5334
|
} catch (e) {
|
|
4999
5335
|
this.#onError(e, run);
|
|
5000
5336
|
return;
|
|
@@ -5120,6 +5456,12 @@ function applyFilter(summaries, filter) {
|
|
|
5120
5456
|
* @stable
|
|
5121
5457
|
*/
|
|
5122
5458
|
function summarizeToolCalls(run) {
|
|
5459
|
+
const startedAtByCallId = /* @__PURE__ */ new Map();
|
|
5460
|
+
for (const event of run.trace) {
|
|
5461
|
+
if (event.type !== "tool.started") continue;
|
|
5462
|
+
const callId = event.data?.["callId"];
|
|
5463
|
+
if (typeof callId === "string" && !startedAtByCallId.has(callId)) startedAtByCallId.set(callId, event.ts);
|
|
5464
|
+
}
|
|
5123
5465
|
const calls = [];
|
|
5124
5466
|
for (const event of run.trace) {
|
|
5125
5467
|
if (event.type !== "tool.completed" && event.type !== "tool.failed") continue;
|
|
@@ -5129,6 +5471,7 @@ function summarizeToolCalls(run) {
|
|
|
5129
5471
|
const args = event.data?.["args"];
|
|
5130
5472
|
const durationMs = event.data?.["durationMs"];
|
|
5131
5473
|
const errorCode = event.data?.["code"];
|
|
5474
|
+
const startedAt = startedAtByCallId.get(callId);
|
|
5132
5475
|
calls.push({
|
|
5133
5476
|
callId,
|
|
5134
5477
|
name,
|
|
@@ -5136,6 +5479,7 @@ function summarizeToolCalls(run) {
|
|
|
5136
5479
|
...typeof args === "string" ? { args } : {},
|
|
5137
5480
|
...typeof durationMs === "number" ? { durationMs } : {},
|
|
5138
5481
|
...typeof errorCode === "string" ? { errorCode } : {},
|
|
5482
|
+
...startedAt !== void 0 ? { startedAt } : {},
|
|
5139
5483
|
...event.type === "tool.failed" && typeof event.message === "string" ? { errorMessage: event.message } : {}
|
|
5140
5484
|
});
|
|
5141
5485
|
}
|
|
@@ -5213,6 +5557,8 @@ var FsSessionStore = class {
|
|
|
5213
5557
|
#fs;
|
|
5214
5558
|
/** id → 该 id 上最后一次变更的完成时点,用于串行化读改写 */
|
|
5215
5559
|
#writes = /* @__PURE__ */ new Map();
|
|
5560
|
+
/** FR-11.4:损坏文件诊断只报一次,避免 list 轮询刷屏 */
|
|
5561
|
+
#warnedDataLoss = false;
|
|
5216
5562
|
constructor(deps) {
|
|
5217
5563
|
this.#root = deps.root.replace(/\/+$/, "");
|
|
5218
5564
|
this.#fs = deps.fs;
|
|
@@ -5242,7 +5588,7 @@ var FsSessionStore = class {
|
|
|
5242
5588
|
...record.modelId !== void 0 ? { modelId: record.modelId } : {},
|
|
5243
5589
|
messages: record.messages
|
|
5244
5590
|
};
|
|
5245
|
-
await this.#fs
|
|
5591
|
+
await atomicWriteText(this.#fs, this.#path(record.id), JSON.stringify(file, null, 2));
|
|
5246
5592
|
}
|
|
5247
5593
|
async #require(id) {
|
|
5248
5594
|
const record = await this.get(id);
|
|
@@ -5262,14 +5608,17 @@ var FsSessionStore = class {
|
|
|
5262
5608
|
let record;
|
|
5263
5609
|
try {
|
|
5264
5610
|
record = parseSessionFile(await this.#fs.readText(entry.path), entry.path);
|
|
5265
|
-
} catch
|
|
5266
|
-
console.warn(`Skipping unreadable session file "${entry.path}": ${e instanceof Error ? e.message : String(e)}`);
|
|
5611
|
+
} catch {
|
|
5267
5612
|
skipped.push(baseName(entry.path));
|
|
5268
5613
|
continue;
|
|
5269
5614
|
}
|
|
5270
5615
|
if (record.archived === true && options.includeArchived !== true) continue;
|
|
5271
5616
|
metas.push(toMeta(record));
|
|
5272
5617
|
}
|
|
5618
|
+
if (skipped.length > 0 && !this.#warnedDataLoss) {
|
|
5619
|
+
this.#warnedDataLoss = true;
|
|
5620
|
+
console.warn(`[webskill] ${skipped.length} session file(s) under ${this.#root} are unreadable (possible data loss from an interrupted write): ${skipped.join(", ")}`);
|
|
5621
|
+
}
|
|
5273
5622
|
metas.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
5274
5623
|
return {
|
|
5275
5624
|
...takeTailPage(metas, options, "session"),
|
|
@@ -5354,4 +5703,4 @@ var FsSessionStore = class {
|
|
|
5354
5703
|
};
|
|
5355
5704
|
|
|
5356
5705
|
//#endregion
|
|
5357
|
-
export { fromVercelStreamPart as $, SerializingMemoryStore as A, bridgeError as B, ProgressiveRouter as C, sealToolCallPairs as Ct, RUN_SNAPSHOT_SCHEMA_VERSION as D,
|
|
5706
|
+
export { fromVercelStreamPart as $, SerializingMemoryStore as A, validateUiSpecNode as At, bridgeError as B, ProgressiveRouter as C, sealToolCallPairs as Ct, RUN_SNAPSHOT_SCHEMA_VERSION as D, toRecordDigests as Dt, READ_SKILL_FILE_TOOL_NAME as E, toLlmToolSpec as Et, USER_PROFILE_PROMPT_HEADER as F, exportUserProfile as G, createScriptContext as H, USER_PROFILE_REFINE_PROMPT as I, extractTodoTraceEvents as J, extractChartSpec as K, WebSkillRuntime as L, USER_PROFILE_EXPORT_VERSION as M, USER_PROFILE_KEY as N, RUN_TRACE_SCHEMA_VERSION as O, toVercelToolSpecs as Ot, USER_PROFILE_NO_INVENTION_RULE as P, fromVercelResult as Q, appendBehaviorRecords as R, OpenAiCompatibleClient as S, scriptToolName as St, READ_SKILL_FILE_TOOL as T, summarizeToolCalls as Tt, createWebSkillApi as U, buildRenderResult as V, diffUserProfile as W, findUnpairedToolCalls as X, extractUiSpecEvents as Y, formatSkillScriptManifest as Z, FsRunTraceStore as _, renderUserProfileContext as _t, AgentLoop as a, mergeProfileEntries as at, GoogleGenAiClient as b, schemaSourceLabel as bt, CapabilityApproval as c, normalizeErrorCode as ct, EMPTY_USER_PROFILE as d, parseBridgeRequest as dt, interruptedToolResult as et, EventBus as f, parseUserProfileExport as ft, FsRunSnapshotStore as g, refineUserProfile as gt, FsMemoryStore as h, readUserProfile as ht, ASK_USER_TOOL_NAME as i, mergeCatalogEntries as it, TraceRecorder as j, SESSION_SCHEMA_VERSION as k, validateUiSpecEvent as kt, DEFAULT_LOOP_LIMITS as l, normalizeToolContent as lt, FsArtifactStore as m, readProfileEntries as mt, ASK_USER_INPUT_SCHEMA as n, isUnsupportedRunSnapshot as nt, AnthropicClient as o, networkPolicyLibSource as ot, FS_SESSION_PAGE_SIZE as p, readBehaviorRecords as pt, extractSkillCandidate as q, ASK_USER_TOOL as r, listSkillScripts as rt, BEHAVIOR_RECORDS_KEY as s, networkUrlHost as st, ALLOWED_TOOLS_EXCLUSION_REASON as t, isNetworkAllowed as tt, DEFAULT_USER_PROFILE_LIMITS as u, normalizeToolError as ut, FsSessionStore as v, resolveToolName as vt, READ_SKILL_FILE_INPUT_SCHEMA as w, summarizeRunUsage as wt, HookRunner as x, schemaToForm as xt, FullDisclosureRouter as y, sampleBehaviorRecords as yt, applyUserProfileImport as z };
|