@webskill/sdk 0.9.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 +5 -1
- package/dist/browser.d.ts +5 -3
- package/dist/browser.js +15 -7
- package/dist/{catalogComponents-DfxxfUvn-T7Ic8QFV.js → catalogComponents-DTcYfpLQ-CcoOaz-Z.js} +1354 -415
- package/dist/{dist-BQe1uglQ.js → dist-1OFC-zax.js} +6 -4
- package/dist/{dist-CFmkV45C.js → dist-BViUeszk.js} +349 -32
- package/dist/{eventTypes-DbOpAECr-BjcjZVms.js → eventTypes-s2uwAcLG-Go3l_dUe.js} +5 -2
- package/dist/governance.d.ts +29 -6
- package/dist/governance.js +45 -7
- package/dist/{index-DXNTIa-6.d.ts → index-B0QPLWPZ.d.ts} +6 -2
- package/dist/{index-lLcCpHE-.d.ts → index-BF4E1a9j.d.ts} +10 -3
- package/dist/{index-znZjobkr.d.ts → index-DtFdMKBX.d.ts} +63 -6
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/mcp.d.ts +18 -3
- package/dist/mcp.js +62 -21
- package/dist/node.d.ts +3 -3
- package/dist/node.js +2 -2
- package/dist/{openUiLibrary-DURlAxjk-Do_yqg3u.js → openUiLibrary-CIrV--Ad-B8zG_91e.js} +3 -3
- package/dist/{skillVersionStore-Bl-ElD45-dMJ8Ybhb.d.ts → skillVersionStore-D-qHk9ZE-DBsYYCWn.d.ts} +13 -5
- package/dist/{testing-Csg5ljNm.js → testing-WPTyXQYt.js} +26 -3
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/dist/{types-B3n0cMZu-BDhheIhX.d.ts → types-CrRcT-LM-DZAp8sWv.d.ts} +36 -3
- package/dist/ui-react.d.ts +11 -2
- package/dist/ui-react.js +119 -38
- 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-BG7kL-Es.js → webskillLitCatalog-BJrphK0y-SGmRXaaO.js} +2 -2
- package/package.json +1 -1
|
@@ -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 {
|
|
@@ -926,14 +1039,22 @@ var AnthropicClient = class {
|
|
|
926
1039
|
const blocks = data.content;
|
|
927
1040
|
if (!Array.isArray(blocks)) throw new Error("response has no content blocks");
|
|
928
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("");
|
|
929
1043
|
const toolCalls = blocks.filter((b) => b["type"] === "tool_use").map((b) => ({
|
|
930
1044
|
id: typeof b["id"] === "string" ? b["id"] : "",
|
|
931
1045
|
name: typeof b["name"] === "string" ? b["name"] : "",
|
|
932
1046
|
arguments: typeof b["input"] === "object" && b["input"] !== null ? b["input"] : {}
|
|
933
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;
|
|
934
1053
|
return {
|
|
935
1054
|
content: text === "" ? void 0 : textParts(text),
|
|
936
1055
|
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
1056
|
+
...thinking === "" ? {} : { thinking },
|
|
1057
|
+
...usage ? { usage } : {},
|
|
937
1058
|
raw: data
|
|
938
1059
|
};
|
|
939
1060
|
} catch (e) {
|
|
@@ -942,6 +1063,18 @@ var AnthropicClient = class {
|
|
|
942
1063
|
}
|
|
943
1064
|
};
|
|
944
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
|
+
}
|
|
945
1078
|
/** parts → GenAI parts(二进制统一走 inlineData) */
|
|
946
1079
|
const toGenAiParts = (parts, where) => parts.map((part) => {
|
|
947
1080
|
switch (part.type) {
|
|
@@ -1015,7 +1148,7 @@ function toGenAiContents(messages) {
|
|
|
1015
1148
|
const toGenAiTools = (tools) => [{ functionDeclarations: tools.map((tool) => ({
|
|
1016
1149
|
name: tool.name,
|
|
1017
1150
|
...tool.description ? { description: tool.description } : {},
|
|
1018
|
-
parameters: sanitizeForVendor(toPortableToolSchema(tool.inputSchema), GOOGLE_SCHEMA_PROFILE, tool.name)
|
|
1151
|
+
parameters: sanitizeForVendor(toPortableToolSchema(stripModelUnfillableParams(tool.inputSchema)), GOOGLE_SCHEMA_PROFILE, tool.name)
|
|
1019
1152
|
})) }];
|
|
1020
1153
|
/** Google GenAI(generateContent / streamGenerateContent)客户端(零依赖 fetch) */
|
|
1021
1154
|
var GoogleGenAiClient = class {
|
|
@@ -1039,9 +1172,11 @@ var GoogleGenAiClient = class {
|
|
|
1039
1172
|
const parts = this.#responseParts(data);
|
|
1040
1173
|
const text = parts.filter((p) => typeof p["text"] === "string").map((p) => String(p["text"])).join("");
|
|
1041
1174
|
const toolCalls = this.#functionCalls(parts);
|
|
1175
|
+
const usage = parseGenAiUsage(data);
|
|
1042
1176
|
return {
|
|
1043
1177
|
content: text === "" ? void 0 : textParts(text),
|
|
1044
1178
|
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
1179
|
+
...usage ? { usage } : {},
|
|
1045
1180
|
raw: data
|
|
1046
1181
|
};
|
|
1047
1182
|
}
|
|
@@ -1053,6 +1188,7 @@ var GoogleGenAiClient = class {
|
|
|
1053
1188
|
const decoder = new TextDecoder();
|
|
1054
1189
|
const reader = res.body.getReader();
|
|
1055
1190
|
const frames = createSseFrameReader();
|
|
1191
|
+
let usage;
|
|
1056
1192
|
const handleFrame = function* (data) {
|
|
1057
1193
|
let chunk;
|
|
1058
1194
|
try {
|
|
@@ -1060,6 +1196,7 @@ var GoogleGenAiClient = class {
|
|
|
1060
1196
|
} catch (e) {
|
|
1061
1197
|
throw new WebSkillError("LLM_REQUEST_FAILED", `Failed to parse LLM stream frame: ${errorMessage(e)}`, data);
|
|
1062
1198
|
}
|
|
1199
|
+
usage = parseGenAiUsage(chunk) ?? usage;
|
|
1063
1200
|
const parts = chunk.candidates?.[0]?.content?.parts;
|
|
1064
1201
|
for (const part of parts ?? []) {
|
|
1065
1202
|
if (typeof part["text"] === "string" && part["text"] !== "") {
|
|
@@ -1091,7 +1228,10 @@ var GoogleGenAiClient = class {
|
|
|
1091
1228
|
type: "tool-calls",
|
|
1092
1229
|
toolCalls
|
|
1093
1230
|
};
|
|
1094
|
-
yield {
|
|
1231
|
+
yield {
|
|
1232
|
+
type: "done",
|
|
1233
|
+
...usage ? { usage } : {}
|
|
1234
|
+
};
|
|
1095
1235
|
}
|
|
1096
1236
|
async #post(input, stream) {
|
|
1097
1237
|
const model = input.model ?? this.#config.model;
|
|
@@ -1152,6 +1292,17 @@ var GoogleGenAiClient = class {
|
|
|
1152
1292
|
return out;
|
|
1153
1293
|
}
|
|
1154
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
|
+
}
|
|
1155
1306
|
function toVercelToolSpecs(tools) {
|
|
1156
1307
|
const out = {};
|
|
1157
1308
|
for (const tool of tools) out[tool.name] = {
|
|
@@ -1160,6 +1311,16 @@ function toVercelToolSpecs(tools) {
|
|
|
1160
1311
|
};
|
|
1161
1312
|
return out;
|
|
1162
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
|
+
}
|
|
1163
1324
|
/** 接受 generateText 的返回值形状:{ text?, toolCalls?: [{ toolCallId, toolName, input|args }] } */
|
|
1164
1325
|
function fromVercelResult(result) {
|
|
1165
1326
|
const r = result;
|
|
@@ -1171,9 +1332,13 @@ function fromVercelResult(result) {
|
|
|
1171
1332
|
arguments: typeof args === "object" && args !== null ? args : {}
|
|
1172
1333
|
};
|
|
1173
1334
|
});
|
|
1335
|
+
const thinking = normalizeThinking(r.reasoningText ?? r.reasoning);
|
|
1336
|
+
const usage = fromVercelUsage(r.usage);
|
|
1174
1337
|
return {
|
|
1175
1338
|
content: typeof r.text === "string" && r.text !== "" ? textParts(r.text) : void 0,
|
|
1176
1339
|
toolCalls: toolCalls.length ? toolCalls : void 0,
|
|
1340
|
+
...thinking !== void 0 ? { thinking } : {},
|
|
1341
|
+
...usage ? { usage } : {},
|
|
1177
1342
|
raw: result
|
|
1178
1343
|
};
|
|
1179
1344
|
}
|
|
@@ -1191,6 +1356,13 @@ function fromVercelStreamPart(part) {
|
|
|
1191
1356
|
delta
|
|
1192
1357
|
} : void 0;
|
|
1193
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
|
+
}
|
|
1194
1366
|
case "tool-call": return {
|
|
1195
1367
|
type: "tool-calls",
|
|
1196
1368
|
toolCalls: [{
|
|
@@ -1199,7 +1371,13 @@ function fromVercelStreamPart(part) {
|
|
|
1199
1371
|
arguments: typeof p["input"] === "object" && p["input"] !== null ? p["input"] : {}
|
|
1200
1372
|
}]
|
|
1201
1373
|
};
|
|
1202
|
-
case "finish":
|
|
1374
|
+
case "finish": {
|
|
1375
|
+
const usage = fromVercelUsage(p["totalUsage"] ?? p["usage"]);
|
|
1376
|
+
return {
|
|
1377
|
+
type: "done",
|
|
1378
|
+
...usage ? { usage } : {}
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1203
1381
|
default: return;
|
|
1204
1382
|
}
|
|
1205
1383
|
}
|
|
@@ -1803,7 +1981,7 @@ const DEFAULT_USER_PROFILE_LIMITS = {
|
|
|
1803
1981
|
recordLimit: 500,
|
|
1804
1982
|
valueMaxChars: 2e3,
|
|
1805
1983
|
sampleLimit: 80,
|
|
1806
|
-
injectMaxBytes:
|
|
1984
|
+
injectMaxBytes: 4e3,
|
|
1807
1985
|
entryLimit: 40
|
|
1808
1986
|
};
|
|
1809
1987
|
/** @experimental */
|
|
@@ -2053,8 +2231,8 @@ function extractSkillCandidate(data) {
|
|
|
2053
2231
|
*/
|
|
2054
2232
|
const DEFAULT_LOOP_LIMITS = {
|
|
2055
2233
|
maxTurns: 10,
|
|
2056
|
-
totalTimeoutMs:
|
|
2057
|
-
toolTimeoutMs:
|
|
2234
|
+
totalTimeoutMs: 36e5,
|
|
2235
|
+
toolTimeoutMs: 6e5
|
|
2058
2236
|
};
|
|
2059
2237
|
const RUN_SNAPSHOT_SCHEMA_VERSION = 3;
|
|
2060
2238
|
/** @experimental */
|
|
@@ -2294,7 +2472,7 @@ var AgentLoop = class {
|
|
|
2294
2472
|
maxTurns: config.maxTurns ?? DEFAULT_LOOP_LIMITS.maxTurns,
|
|
2295
2473
|
totalTimeoutMs: config.totalTimeoutMs ?? DEFAULT_LOOP_LIMITS.totalTimeoutMs,
|
|
2296
2474
|
toolTimeoutMs: config.toolTimeoutMs ?? DEFAULT_LOOP_LIMITS.toolTimeoutMs,
|
|
2297
|
-
toolResultMaxBytes: config.toolResultMaxBytes ??
|
|
2475
|
+
toolResultMaxBytes: config.toolResultMaxBytes ?? 256e3,
|
|
2298
2476
|
paramHistoryLimit: config.paramHistoryLimit ?? 50,
|
|
2299
2477
|
toolCallingDisabled: config.toolCallingDisabled ?? false,
|
|
2300
2478
|
maxUnknownToolRetries: config.maxUnknownToolRetries ?? 2,
|
|
@@ -2357,6 +2535,7 @@ var AgentLoop = class {
|
|
|
2357
2535
|
surfacePatchCount: 0,
|
|
2358
2536
|
processedSurfaceActionNonces: /* @__PURE__ */ new Set(),
|
|
2359
2537
|
emittedToolEvents: /* @__PURE__ */ new Set(),
|
|
2538
|
+
artifactPaths: /* @__PURE__ */ new Set(),
|
|
2360
2539
|
unknownToolCalls: 0,
|
|
2361
2540
|
startMs,
|
|
2362
2541
|
pausedMs: 0,
|
|
@@ -2507,6 +2686,23 @@ var AgentLoop = class {
|
|
|
2507
2686
|
const code = e instanceof WebSkillError && (e.code === "LLM_UNAVAILABLE" || e.code === "LLM_REQUEST_FAILED") ? e.code : "LLM_REQUEST_FAILED";
|
|
2508
2687
|
return finish("failed", "llm-error", messageOf(e), code);
|
|
2509
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
|
+
}
|
|
2510
2706
|
const responseText = partsToText(response.content);
|
|
2511
2707
|
if (!llm.stream && responseText !== "") await this.#deps.uiBridge?.onTextDelta?.(state.runId, responseText);
|
|
2512
2708
|
trace.record("llm.response", { data: {
|
|
@@ -2514,6 +2710,11 @@ var AgentLoop = class {
|
|
|
2514
2710
|
hasToolCalls: Boolean(response.toolCalls?.length),
|
|
2515
2711
|
contentLength: responseText.length
|
|
2516
2712
|
} });
|
|
2713
|
+
if (response.usage) trace.record("llm.usage", { data: {
|
|
2714
|
+
turn,
|
|
2715
|
+
inputTokens: response.usage.inputTokens,
|
|
2716
|
+
outputTokens: response.usage.outputTokens
|
|
2717
|
+
} });
|
|
2517
2718
|
if (!response.toolCalls?.length) {
|
|
2518
2719
|
messages.push({
|
|
2519
2720
|
role: "assistant",
|
|
@@ -2723,6 +2924,7 @@ var AgentLoop = class {
|
|
|
2723
2924
|
surfacePatchCount: 0,
|
|
2724
2925
|
processedSurfaceActionNonces: new Set(snapshot.processedSurfaceActionNonces ?? []),
|
|
2725
2926
|
emittedToolEvents: /* @__PURE__ */ new Set(),
|
|
2927
|
+
artifactPaths: /* @__PURE__ */ new Set(),
|
|
2726
2928
|
unknownToolCalls: 0,
|
|
2727
2929
|
startMs,
|
|
2728
2930
|
pausedMs: snapshot.pausedMs ?? 0,
|
|
@@ -2843,6 +3045,7 @@ var AgentLoop = class {
|
|
|
2843
3045
|
/**
|
|
2844
3046
|
* 流式 LLM 调用:text-delta 仅在 EventBus 有订阅者时发射 llm.delta 事件,
|
|
2845
3047
|
* 并同步转发 uiBridge.onTextDelta;tool-calls/done 组装为 LlmResponse 走原有分支。
|
|
3048
|
+
* thinking-delta 同理累积成 `response.thinking`(每轮一段,UI-UX5 #32)。
|
|
2846
3049
|
*/
|
|
2847
3050
|
async #completeStreaming(llm, state, input) {
|
|
2848
3051
|
const emitDelta = (delta) => {
|
|
@@ -2859,18 +3062,41 @@ var AgentLoop = class {
|
|
|
2859
3062
|
});
|
|
2860
3063
|
this.#deps.uiBridge?.onTextDelta?.(state.runId, delta);
|
|
2861
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
|
+
};
|
|
2862
3078
|
let content = "";
|
|
3079
|
+
let thinking = "";
|
|
2863
3080
|
let doneContent;
|
|
3081
|
+
let usage;
|
|
2864
3082
|
const toolCalls = [];
|
|
2865
3083
|
for await (const event of llm.stream(input)) if (event.type === "text-delta") {
|
|
2866
3084
|
content += event.delta;
|
|
2867
3085
|
emitDelta(event.delta);
|
|
3086
|
+
} else if (event.type === "thinking-delta") {
|
|
3087
|
+
thinking += event.delta;
|
|
3088
|
+
emitThinkingDelta(event.delta);
|
|
2868
3089
|
} else if (event.type === "tool-calls") toolCalls.push(...event.toolCalls);
|
|
2869
|
-
else if (event.type === "done")
|
|
3090
|
+
else if (event.type === "done") {
|
|
3091
|
+
doneContent = event.content;
|
|
3092
|
+
usage = event.usage ?? usage;
|
|
3093
|
+
}
|
|
2870
3094
|
const text = doneContent ?? content;
|
|
2871
3095
|
return {
|
|
2872
3096
|
content: text === "" ? void 0 : textParts(text),
|
|
2873
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : void 0
|
|
3097
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
3098
|
+
...thinking === "" ? {} : { thinking },
|
|
3099
|
+
...usage ? { usage } : {}
|
|
2874
3100
|
};
|
|
2875
3101
|
}
|
|
2876
3102
|
/** 生命周期接线:更新 phase、发事件、跑钩子 */
|
|
@@ -3097,10 +3323,13 @@ var AgentLoop = class {
|
|
|
3097
3323
|
});
|
|
3098
3324
|
this.#emitTool(state, "failed", call, result.error?.code);
|
|
3099
3325
|
}
|
|
3100
|
-
for (const artifact of result.artifacts ?? [])
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
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
|
+
}
|
|
3104
3333
|
if (state.unknownToolCalls > this.#config.maxUnknownToolRetries) throw new RunTerminated({
|
|
3105
3334
|
status: "failed",
|
|
3106
3335
|
reason: "tool-resolution-exhausted",
|
|
@@ -3116,7 +3345,12 @@ var AgentLoop = class {
|
|
|
3116
3345
|
state.trace.record("run.warning", { message: "UiBridge does not support renderSurface; UI surface was not rendered" });
|
|
3117
3346
|
return;
|
|
3118
3347
|
}
|
|
3119
|
-
|
|
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
|
+
}
|
|
3120
3354
|
if (attributed.type === "patch") this.#consumeSurfacePatchBudget(state);
|
|
3121
3355
|
await bridge.renderSurface(attributed);
|
|
3122
3356
|
state.surfaceEvents.push(structuredClone(attributed));
|
|
@@ -3159,6 +3393,44 @@ var AgentLoop = class {
|
|
|
3159
3393
|
}))
|
|
3160
3394
|
};
|
|
3161
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
|
+
}
|
|
3162
3434
|
/** Awaits the single action emitted with the most recently persisted tool result. */
|
|
3163
3435
|
async #drainSurfaceAction(state) {
|
|
3164
3436
|
const request = state.pendingSurfaceAction;
|
|
@@ -3437,7 +3709,7 @@ var AgentLoop = class {
|
|
|
3437
3709
|
}))];
|
|
3438
3710
|
}
|
|
3439
3711
|
/**
|
|
3440
|
-
* 工具结果回喂序列化:超过 toolResultMaxBytes(默认
|
|
3712
|
+
* 工具结果回喂序列化:超过 toolResultMaxBytes(默认 256KB)时头尾保留截断,
|
|
3441
3713
|
* 完整内容经 artifactStore 落 artifact,回喂摘要含 artifact id。
|
|
3442
3714
|
*/
|
|
3443
3715
|
async #serializeToolResult(call, result, state) {
|
|
@@ -4930,6 +5202,38 @@ var CapabilityApproval = class CapabilityApproval {
|
|
|
4930
5202
|
return "allowed";
|
|
4931
5203
|
}
|
|
4932
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
|
+
}
|
|
4933
5237
|
const RUN_TRACE_SCHEMA_VERSION = 1;
|
|
4934
5238
|
/** 终止原因:取最后一条 run.completed/cancelled/failed 事件的 data.reason */
|
|
4935
5239
|
function extractEndReason(events) {
|
|
@@ -4944,6 +5248,7 @@ function extractEndReason(events) {
|
|
|
4944
5248
|
function summarize(trace) {
|
|
4945
5249
|
const endReason = extractEndReason(trace.events);
|
|
4946
5250
|
const durationMs = trace.endedAt === void 0 ? void 0 : Date.parse(trace.endedAt) - Date.parse(trace.startedAt);
|
|
5251
|
+
const runUsage = summarizeRunUsage(trace.events);
|
|
4947
5252
|
return {
|
|
4948
5253
|
runId: trace.runId,
|
|
4949
5254
|
startedAt: trace.startedAt,
|
|
@@ -4954,7 +5259,11 @@ function summarize(trace) {
|
|
|
4954
5259
|
...trace.sessionId !== void 0 ? { sessionId: trace.sessionId } : {},
|
|
4955
5260
|
...trace.endedAt !== void 0 ? { endedAt: trace.endedAt } : {},
|
|
4956
5261
|
...endReason !== void 0 ? { endReason } : {},
|
|
4957
|
-
...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
|
+
} } : {}
|
|
4958
5267
|
};
|
|
4959
5268
|
}
|
|
4960
5269
|
function parseTraceFile(raw, path) {
|
|
@@ -5147,6 +5456,12 @@ function applyFilter(summaries, filter) {
|
|
|
5147
5456
|
* @stable
|
|
5148
5457
|
*/
|
|
5149
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
|
+
}
|
|
5150
5465
|
const calls = [];
|
|
5151
5466
|
for (const event of run.trace) {
|
|
5152
5467
|
if (event.type !== "tool.completed" && event.type !== "tool.failed") continue;
|
|
@@ -5156,6 +5471,7 @@ function summarizeToolCalls(run) {
|
|
|
5156
5471
|
const args = event.data?.["args"];
|
|
5157
5472
|
const durationMs = event.data?.["durationMs"];
|
|
5158
5473
|
const errorCode = event.data?.["code"];
|
|
5474
|
+
const startedAt = startedAtByCallId.get(callId);
|
|
5159
5475
|
calls.push({
|
|
5160
5476
|
callId,
|
|
5161
5477
|
name,
|
|
@@ -5163,6 +5479,7 @@ function summarizeToolCalls(run) {
|
|
|
5163
5479
|
...typeof args === "string" ? { args } : {},
|
|
5164
5480
|
...typeof durationMs === "number" ? { durationMs } : {},
|
|
5165
5481
|
...typeof errorCode === "string" ? { errorCode } : {},
|
|
5482
|
+
...startedAt !== void 0 ? { startedAt } : {},
|
|
5166
5483
|
...event.type === "tool.failed" && typeof event.message === "string" ? { errorMessage: event.message } : {}
|
|
5167
5484
|
});
|
|
5168
5485
|
}
|
|
@@ -5386,4 +5703,4 @@ var FsSessionStore = class {
|
|
|
5386
5703
|
};
|
|
5387
5704
|
|
|
5388
5705
|
//#endregion
|
|
5389
|
-
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 };
|