@co0ontty/wand 4.72.2-beta.g6c641f4 → 4.72.2
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/build-info.json +4 -4
- package/dist/message-truncator.js +0 -6
- package/dist/server-session-routes.js +6 -42
- package/dist/session-transport.js +1 -1
- package/dist/structured-client-protocol.d.ts +1 -3
- package/dist/structured-client-protocol.js +3 -46
- package/dist/structured-content.d.ts +3 -20
- package/dist/structured-content.js +1 -80
- package/dist/structured-pi-adapter.js +2 -18
- package/dist/structured-session-manager.js +2 -2
- package/dist/web-ui/content/scripts.js +1 -1
- package/dist/web-ui/embedded-assets.js +1 -1
- package/dist/ws-broadcast.js +4 -4
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-09-22T23:
|
|
4
|
-
"version": "4.72.2
|
|
5
|
-
"channel": "
|
|
2
|
+
"commit": "cc5d57ee24c38039f381a1bbda3b8d4a95fc26b4",
|
|
3
|
+
"builtAt": "2026-09-22T23:09:41.272Z",
|
|
4
|
+
"version": "4.72.2",
|
|
5
|
+
"channel": "stable"
|
|
6
6
|
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Cards that are collapsed by default have their large results replaced
|
|
4
4
|
* with a summary, and clients fetch full content on-demand via API.
|
|
5
5
|
*/
|
|
6
|
-
import { contentHasStructuredImage } from "./structured-content.js";
|
|
7
6
|
const TRUNCATION_THRESHOLD = 200;
|
|
8
7
|
const SUMMARY_LENGTH = 100;
|
|
9
8
|
/**
|
|
@@ -163,11 +162,6 @@ export function truncateMessagesForTransport(messages, cardDefaults, streamingTu
|
|
|
163
162
|
const toolName = toolNameMap.get(result.tool_use_id) || "";
|
|
164
163
|
if (!isToolDefaultCollapsed(toolName, cardDefaults))
|
|
165
164
|
return block;
|
|
166
|
-
// 带图片的 tool_result 永不截断:图片 part 被 JSON.stringify 截半后客户端再也
|
|
167
|
-
// 抽不出 data URI(线上表现就是「读图不显示,只剩一串 base64 残文」)。图片要么
|
|
168
|
-
// 整块送达(客户端会强制展开渲染),要么由 _truncated + 懒加载单独取回。
|
|
169
|
-
if (contentHasStructuredImage(result.content))
|
|
170
|
-
return block;
|
|
171
165
|
const contentStr = getContentString(result.content);
|
|
172
166
|
if (contentStr.length <= TRUNCATION_THRESHOLD)
|
|
173
167
|
return block;
|
|
@@ -17,7 +17,6 @@ import { sendRouteError } from "./server-request.js";
|
|
|
17
17
|
import { registerStructuredResumeRoutes } from "./server-resume-routes.js";
|
|
18
18
|
import { addHiddenSessionIds, removeHiddenSessionIds } from "./session-registry.js";
|
|
19
19
|
import { enrichStructuredMessages, WAND_PROTOCOL_VERSION } from "./structured-client-protocol.js";
|
|
20
|
-
import { asRecord, isStructuredImagePart } from "./structured-content.js";
|
|
21
20
|
import { syncWorkspaceTaskToBoard } from "./wand-task-sync.js";
|
|
22
21
|
import { buildDirectoryTree, normalizeSessionDirectory, } from "./session-directory-tree.js";
|
|
23
22
|
import { projectCwdForSession, renameWorkspaceDirectory, resolveWorkspaceIdForNewSession, } from "./workspace-binding.js";
|
|
@@ -352,7 +351,7 @@ function isMergeActionAllowed(snapshot) {
|
|
|
352
351
|
}
|
|
353
352
|
export function registerSessionRoutes(app, processes, structured, storage, defaultMode, config, sessions, onSessionCreated) {
|
|
354
353
|
const sessionResponseDTO = (snapshot) => {
|
|
355
|
-
const windowed = windowMessagesForTransport(enrichStructuredMessages(snapshot.messages ?? []
|
|
354
|
+
const windowed = windowMessagesForTransport(enrichStructuredMessages(snapshot.messages ?? []), config.cardDefaults ?? {});
|
|
356
355
|
return toSessionDetailDTO(snapshot, {
|
|
357
356
|
messages: windowed.messages,
|
|
358
357
|
messageOffset: windowed.messageOffset,
|
|
@@ -689,41 +688,6 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
689
688
|
}
|
|
690
689
|
res.status(404).json({ error: "未找到该工具结果。" });
|
|
691
690
|
});
|
|
692
|
-
// ── Inline tool image endpoint ──
|
|
693
|
-
// tool_result 里内联的 base64 图片在传输层被改写为这个 URL(避免把整张图的 base64
|
|
694
|
-
// 塞进每次快照 / WS 帧)。各端用同一套带鉴权的取图通道按需加载。
|
|
695
|
-
app.get("/api/sessions/:id/tool-images/:toolUseId/:index", (req, res) => {
|
|
696
|
-
const snapshot = sessions.get(req.params.id);
|
|
697
|
-
if (!snapshot) {
|
|
698
|
-
res.status(404).json({ error: "未找到该会话。" });
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
const toolUseId = req.params.toolUseId;
|
|
702
|
-
const index = Number.parseInt(req.params.index, 10);
|
|
703
|
-
if (!Number.isFinite(index) || index < 0) {
|
|
704
|
-
res.status(400).json({ error: "无效的图片序号。" });
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
707
|
-
for (const turn of snapshot.messages ?? []) {
|
|
708
|
-
for (const block of turn.content) {
|
|
709
|
-
if (block.type !== "tool_result" || block.tool_use_id !== toolUseId || !Array.isArray(block.content))
|
|
710
|
-
continue;
|
|
711
|
-
const part = block.content.filter(isStructuredImagePart)[index];
|
|
712
|
-
const source = asRecord(part?.source);
|
|
713
|
-
const data = typeof source?.data === "string" ? source.data : undefined;
|
|
714
|
-
if (source?.type !== "base64" || !data) {
|
|
715
|
-
res.status(404).json({ error: "未找到该图片。" });
|
|
716
|
-
return;
|
|
717
|
-
}
|
|
718
|
-
res.setHeader("Content-Type", typeof source.media_type === "string" ? source.media_type : "image/png");
|
|
719
|
-
res.setHeader("Cache-Control", "private, max-age=3600");
|
|
720
|
-
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
721
|
-
res.send(Buffer.from(data, "base64"));
|
|
722
|
-
return;
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
res.status(404).json({ error: "未找到该工具结果。" });
|
|
726
|
-
});
|
|
727
691
|
app.post("/api/sessions/:id/worktree/merge/check", asyncRoute(async (req, res) => {
|
|
728
692
|
try {
|
|
729
693
|
const current = requireWorktreeSession(sessions.getLatest(req.params.id));
|
|
@@ -1052,7 +1016,7 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
1052
1016
|
const rawBudget = req.query.blockBudget;
|
|
1053
1017
|
if (typeof rawBudget === "string" && /^\d+$/.test(rawBudget) && Number(rawBudget) > 0) {
|
|
1054
1018
|
const blockBudget = parseBoundedInteger(rawBudget, 1, 1, 2_000);
|
|
1055
|
-
const windowed = blockWindowMessagesForTransport(enrichStructuredMessages(snapshot.messages ?? []
|
|
1019
|
+
const windowed = blockWindowMessagesForTransport(enrichStructuredMessages(snapshot.messages ?? []), config.cardDefaults ?? {}, blockBudget);
|
|
1056
1020
|
res.json(toSessionDetailDTO(snapshot, {
|
|
1057
1021
|
output: transcriptOutput,
|
|
1058
1022
|
messages: windowed.messages,
|
|
@@ -1064,7 +1028,7 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
1064
1028
|
return;
|
|
1065
1029
|
}
|
|
1066
1030
|
// 与 WS init 对齐:只回最近一窗 turn + offset/total,更早的走 /messages 翻页。
|
|
1067
|
-
const windowed = windowMessagesForTransport(enrichStructuredMessages(snapshot.messages ?? []
|
|
1031
|
+
const windowed = windowMessagesForTransport(enrichStructuredMessages(snapshot.messages ?? []), config.cardDefaults ?? {});
|
|
1068
1032
|
res.json(toSessionDetailDTO(snapshot, {
|
|
1069
1033
|
output: transcriptOutput,
|
|
1070
1034
|
messages: windowed.messages,
|
|
@@ -1084,7 +1048,7 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
1084
1048
|
res.status(404).json({ error: "未找到该会话,可能已被删除。" });
|
|
1085
1049
|
return;
|
|
1086
1050
|
}
|
|
1087
|
-
const all = enrichStructuredMessages(snapshot.messages ?? []
|
|
1051
|
+
const all = enrichStructuredMessages(snapshot.messages ?? []);
|
|
1088
1052
|
const total = all.length;
|
|
1089
1053
|
// 块级翻页(iOS):?turn=<i>&blockOffset=<当前 leading 偏移>&blockLimit=<N>
|
|
1090
1054
|
// 取该 turn 的 [start, blockOffset) 段(start = max(0, blockOffset - blockLimit))。
|
|
@@ -1105,7 +1069,7 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
1105
1069
|
const blocks = enrichStructuredMessages([{
|
|
1106
1070
|
...turn,
|
|
1107
1071
|
content: sliceTurnBlocksForTransport(turn, blockStart, blockEnd, config.cardDefaults ?? {}),
|
|
1108
|
-
}]
|
|
1072
|
+
}])[0].content;
|
|
1109
1073
|
res.json({ wandProtocolVersion: WAND_PROTOCOL_VERSION, turnIndex, blocks, blockOffset: blockStart, blockTotal });
|
|
1110
1074
|
return;
|
|
1111
1075
|
}
|
|
@@ -1114,7 +1078,7 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
1114
1078
|
const limit = Math.min(Math.max(Number.isFinite(rawLimit) ? rawLimit : 40, 1), 200);
|
|
1115
1079
|
const offset = Math.min(Math.max(Number.isFinite(rawOffset) ? rawOffset : 0, 0), total);
|
|
1116
1080
|
const end = Math.min(offset + limit, total);
|
|
1117
|
-
const slice = enrichStructuredMessages(truncateMessagesForTransport(all.slice(offset, end), config.cardDefaults ?? {})
|
|
1081
|
+
const slice = enrichStructuredMessages(truncateMessagesForTransport(all.slice(offset, end), config.cardDefaults ?? {}));
|
|
1118
1082
|
res.json({ wandProtocolVersion: WAND_PROTOCOL_VERSION, messages: slice, offset, total });
|
|
1119
1083
|
});
|
|
1120
1084
|
app.post("/api/sessions/:id/resume", asyncRoute(async (req, res) => {
|
|
@@ -87,7 +87,7 @@ export function toSessionDetailDTO(snapshot, options = {}) {
|
|
|
87
87
|
outputOffset,
|
|
88
88
|
outputTotal: rawOutput.length,
|
|
89
89
|
outputTruncated: outputOffset > 0,
|
|
90
|
-
...(options.messages !== undefined ? { messages: enrichStructuredMessages(options.messages
|
|
90
|
+
...(options.messages !== undefined ? { messages: enrichStructuredMessages(options.messages) } : {}),
|
|
91
91
|
...(options.messageOffset !== undefined ? { messageOffset: options.messageOffset } : {}),
|
|
92
92
|
...(options.messageTotal !== undefined ? { messageTotal: options.messageTotal } : {}),
|
|
93
93
|
...(options.leadingBlockOffset !== undefined ? { leadingBlockOffset: options.leadingBlockOffset } : {}),
|
|
@@ -3,7 +3,5 @@ export declare const WAND_PROTOCOL_VERSION = 2;
|
|
|
3
3
|
/**
|
|
4
4
|
* Add Wand-owned semantics without mutating persisted provider blocks.
|
|
5
5
|
* This is the external interface consumed by every client.
|
|
6
|
-
*
|
|
7
|
-
* 传 sessionId 时额外把内联图片改写成取图 URL(见 rewriteInlineToolImageUrls)。
|
|
8
6
|
*/
|
|
9
|
-
export declare function enrichStructuredMessages(messages: ConversationTurn[]
|
|
7
|
+
export declare function enrichStructuredMessages(messages: ConversationTurn[]): ConversationTurn[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { asRecord
|
|
1
|
+
import { asRecord } from "./structured-content.js";
|
|
2
2
|
export const WAND_PROTOCOL_VERSION = 2;
|
|
3
3
|
function text(value) {
|
|
4
4
|
return typeof value === "string" && value ? value : undefined;
|
|
@@ -190,54 +190,11 @@ function tasksFromSegment(messages, start, end) {
|
|
|
190
190
|
targetId,
|
|
191
191
|
};
|
|
192
192
|
}
|
|
193
|
-
/**
|
|
194
|
-
* 把 tool_result 里的内联 base64 图片换成「按会话取图」的 URL。
|
|
195
|
-
*
|
|
196
|
-
* 为什么不能把 base64 直接下发:读一张截图动辄 1–3MB,base64 后更大,会撑爆移动端
|
|
197
|
-
* 单帧上限(iOS 默认 1MiB)导致反复断连;而且同一张图在每条快照里重复传。换成 URL
|
|
198
|
-
* 后各端用同一套带鉴权的取图通道按需加载(Web 走 <img>,原生走各自 ImageLoader)。
|
|
199
|
-
* source 本来就是 url 的图片不动,保持幂等。
|
|
200
|
-
*/
|
|
201
|
-
function rewriteInlineToolImageUrls(messages, sessionId) {
|
|
202
|
-
return messages.map((turn) => {
|
|
203
|
-
let turnChanged = false;
|
|
204
|
-
const content = turn.content.map((block) => {
|
|
205
|
-
if (block.type !== "tool_result" || !Array.isArray(block.content))
|
|
206
|
-
return block;
|
|
207
|
-
let imageIndex = -1;
|
|
208
|
-
let blockChanged = false;
|
|
209
|
-
const parts = block.content.map((part) => {
|
|
210
|
-
if (!isStructuredImagePart(part))
|
|
211
|
-
return part;
|
|
212
|
-
imageIndex += 1;
|
|
213
|
-
const source = asRecord(part.source);
|
|
214
|
-
if (source?.type !== "base64" || typeof source.data !== "string")
|
|
215
|
-
return part;
|
|
216
|
-
blockChanged = true;
|
|
217
|
-
return {
|
|
218
|
-
type: "image",
|
|
219
|
-
source: {
|
|
220
|
-
type: "url",
|
|
221
|
-
url: `/api/sessions/${encodeURIComponent(sessionId)}/tool-images/${encodeURIComponent(block.tool_use_id)}/${imageIndex}`,
|
|
222
|
-
...(typeof source.media_type === "string" ? { media_type: source.media_type } : {}),
|
|
223
|
-
},
|
|
224
|
-
};
|
|
225
|
-
});
|
|
226
|
-
if (!blockChanged)
|
|
227
|
-
return block;
|
|
228
|
-
turnChanged = true;
|
|
229
|
-
return { ...block, content: parts };
|
|
230
|
-
});
|
|
231
|
-
return turnChanged ? { ...turn, content } : turn;
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
193
|
/**
|
|
235
194
|
* Add Wand-owned semantics without mutating persisted provider blocks.
|
|
236
195
|
* This is the external interface consumed by every client.
|
|
237
|
-
*
|
|
238
|
-
* 传 sessionId 时额外把内联图片改写成取图 URL(见 rewriteInlineToolImageUrls)。
|
|
239
196
|
*/
|
|
240
|
-
export function enrichStructuredMessages(messages
|
|
197
|
+
export function enrichStructuredMessages(messages) {
|
|
241
198
|
const enriched = stampDerivedSubagents(messages).map((turn) => ({
|
|
242
199
|
...turn,
|
|
243
200
|
content: turn.content.map((block) => {
|
|
@@ -274,5 +231,5 @@ export function enrichStructuredMessages(messages, sessionId) {
|
|
|
274
231
|
}
|
|
275
232
|
segmentStart = i;
|
|
276
233
|
}
|
|
277
|
-
return
|
|
234
|
+
return enriched;
|
|
278
235
|
}
|
|
@@ -1,24 +1,7 @@
|
|
|
1
1
|
/** 把 unknown 收敛成普通对象;数组 / null / 原始值都返回 null。 */
|
|
2
2
|
export declare function asRecord(value: unknown): Record<string, unknown> | null;
|
|
3
|
-
|
|
3
|
+
/** Preserve both Responses content parts and arbitrary structured tool output. */
|
|
4
|
+
export declare function normalizeStructuredToolResultContent(content: unknown): string | Array<{
|
|
4
5
|
type: string;
|
|
5
6
|
[key: string]: unknown;
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* 各家 provider 的图片 part 形态不同:
|
|
9
|
-
* · Anthropic:`{ type:"image", source:{ type:"base64", media_type, data } }` / `source:{ type:"url", url }`
|
|
10
|
-
* · OpenAI Responses:`{ type:"image_url", image_url:{ url } }` / `image_url:"…"`
|
|
11
|
-
* · Pi:`{ type:"image", data, mimeType }`
|
|
12
|
-
* 只要带图片语义就算,具体归一化交给 canonicalizeStructuredImagePart。
|
|
13
|
-
*/
|
|
14
|
-
export declare function isStructuredImagePart(value: unknown): boolean;
|
|
15
|
-
/**
|
|
16
|
-
* 统一成客户端认识的规范形态(Web / Android / iOS / macOS 都按
|
|
17
|
-
* `{ type:"image", source:{ type:"base64"|"url", … } }` 解析)。识别不了返回 null,
|
|
18
|
-
* 由调用方原样保留,避免丢信息。
|
|
19
|
-
*/
|
|
20
|
-
export declare function canonicalizeStructuredImagePart(value: unknown): StructuredContentPart | null;
|
|
21
|
-
/** content(字符串 / part 数组)里是否含图片 part。 */
|
|
22
|
-
export declare function contentHasStructuredImage(content: unknown): boolean;
|
|
23
|
-
/** Preserve both Responses content parts and arbitrary structured tool output. */
|
|
24
|
-
export declare function normalizeStructuredToolResultContent(content: unknown): string | StructuredContentPart[];
|
|
7
|
+
}>;
|
|
@@ -4,91 +4,12 @@ export function asRecord(value) {
|
|
|
4
4
|
? value
|
|
5
5
|
: null;
|
|
6
6
|
}
|
|
7
|
-
function stringValue(value) {
|
|
8
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* 各家 provider 的图片 part 形态不同:
|
|
12
|
-
* · Anthropic:`{ type:"image", source:{ type:"base64", media_type, data } }` / `source:{ type:"url", url }`
|
|
13
|
-
* · OpenAI Responses:`{ type:"image_url", image_url:{ url } }` / `image_url:"…"`
|
|
14
|
-
* · Pi:`{ type:"image", data, mimeType }`
|
|
15
|
-
* 只要带图片语义就算,具体归一化交给 canonicalizeStructuredImagePart。
|
|
16
|
-
*/
|
|
17
|
-
export function isStructuredImagePart(value) {
|
|
18
|
-
const part = asRecord(value);
|
|
19
|
-
if (!part)
|
|
20
|
-
return false;
|
|
21
|
-
if (part.type === "image_url")
|
|
22
|
-
return !!imageUrlOf(part);
|
|
23
|
-
if (part.type !== "image")
|
|
24
|
-
return false;
|
|
25
|
-
const source = asRecord(part.source);
|
|
26
|
-
if (source && (stringValue(source.data) || stringValue(source.url)))
|
|
27
|
-
return true;
|
|
28
|
-
return !!(stringValue(part.data) || stringValue(part.url));
|
|
29
|
-
}
|
|
30
|
-
function imageUrlOf(part) {
|
|
31
|
-
const raw = part.image_url;
|
|
32
|
-
if (typeof raw === "string")
|
|
33
|
-
return stringValue(raw);
|
|
34
|
-
return stringValue(asRecord(raw)?.url);
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* 统一成客户端认识的规范形态(Web / Android / iOS / macOS 都按
|
|
38
|
-
* `{ type:"image", source:{ type:"base64"|"url", … } }` 解析)。识别不了返回 null,
|
|
39
|
-
* 由调用方原样保留,避免丢信息。
|
|
40
|
-
*/
|
|
41
|
-
export function canonicalizeStructuredImagePart(value) {
|
|
42
|
-
const part = asRecord(value);
|
|
43
|
-
if (!part)
|
|
44
|
-
return null;
|
|
45
|
-
if (part.type === "image_url") {
|
|
46
|
-
const url = imageUrlOf(part);
|
|
47
|
-
return url ? { type: "image", source: { type: "url", url } } : null;
|
|
48
|
-
}
|
|
49
|
-
if (part.type !== "image")
|
|
50
|
-
return null;
|
|
51
|
-
const source = asRecord(part.source);
|
|
52
|
-
if (source) {
|
|
53
|
-
const data = stringValue(source.data);
|
|
54
|
-
if (source.type === "base64" && data) {
|
|
55
|
-
return {
|
|
56
|
-
type: "image",
|
|
57
|
-
source: { type: "base64", media_type: stringValue(source.media_type) ?? "image/png", data },
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
const url = stringValue(source.url);
|
|
61
|
-
if (url)
|
|
62
|
-
return { type: "image", source: { type: "url", url } };
|
|
63
|
-
}
|
|
64
|
-
const data = stringValue(part.data);
|
|
65
|
-
if (data) {
|
|
66
|
-
const mediaType = stringValue(part.mimeType)
|
|
67
|
-
?? stringValue(part.mime_type)
|
|
68
|
-
?? stringValue(source?.media_type)
|
|
69
|
-
?? "image/png";
|
|
70
|
-
return { type: "image", source: { type: "base64", media_type: mediaType, data } };
|
|
71
|
-
}
|
|
72
|
-
const url = stringValue(part.url);
|
|
73
|
-
if (url)
|
|
74
|
-
return { type: "image", source: { type: "url", url } };
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
/** content(字符串 / part 数组)里是否含图片 part。 */
|
|
78
|
-
export function contentHasStructuredImage(content) {
|
|
79
|
-
if (!Array.isArray(content))
|
|
80
|
-
return false;
|
|
81
|
-
return content.some((part) => isStructuredImagePart(part));
|
|
82
|
-
}
|
|
83
7
|
/** Preserve both Responses content parts and arbitrary structured tool output. */
|
|
84
8
|
export function normalizeStructuredToolResultContent(content) {
|
|
85
9
|
if (typeof content === "string")
|
|
86
10
|
return content;
|
|
87
11
|
if (Array.isArray(content)) {
|
|
88
|
-
const parts = content
|
|
89
|
-
.filter((item) => !!item && typeof item === "object" && typeof item.type === "string")
|
|
90
|
-
// 图片 part 归一化后客户端才能跨端渲染;非图片 part 原样保留。
|
|
91
|
-
.map((part) => canonicalizeStructuredImagePart(part) ?? part);
|
|
12
|
+
const parts = content.filter((item) => !!item && typeof item === "object" && typeof item.type === "string");
|
|
92
13
|
if (parts.length === content.length)
|
|
93
14
|
return parts;
|
|
94
15
|
try {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { startStructuredCli } from "./structured-exec-pump.js";
|
|
3
|
-
import { asRecord
|
|
3
|
+
import { asRecord } from "./structured-content.js";
|
|
4
4
|
import { thinkingEffortToPiLevel } from "./structured-provider-common.js";
|
|
5
5
|
export function isMissingPiSession(error, sessionId) {
|
|
6
6
|
return !!sessionId && !!error?.includes(`No session found matching '${sessionId}'`);
|
|
@@ -17,22 +17,6 @@ function textContent(value) {
|
|
|
17
17
|
return item.text;
|
|
18
18
|
return textContent(item.content);
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Pi 的工具结果是 `{ content: [ text…, image… ] }`。图片 part 不能像以前那样只抽文本
|
|
22
|
-
* 丢掉——读图 / 截图全靠它,统一交给 normalizeStructuredToolResultContent 归一化。
|
|
23
|
-
* 纯文本结果仍压成字符串,保持既有形态。
|
|
24
|
-
*/
|
|
25
|
-
function piToolResultContent(value) {
|
|
26
|
-
const record = asRecord(value);
|
|
27
|
-
const raw = record && "content" in record ? record.content : value;
|
|
28
|
-
const normalized = normalizeStructuredToolResultContent(raw);
|
|
29
|
-
if (typeof normalized === "string")
|
|
30
|
-
return normalized;
|
|
31
|
-
if (normalized.length > 0 && normalized.every((part) => part.type === "text" && typeof part.text === "string")) {
|
|
32
|
-
return normalized.map((part) => part.text).filter(Boolean).join("\n");
|
|
33
|
-
}
|
|
34
|
-
return normalized;
|
|
35
|
-
}
|
|
36
20
|
export function buildPiArgs(session, prompt) {
|
|
37
21
|
const args = ["--mode", "json", "--print"];
|
|
38
22
|
const model = session.selectedModel?.trim();
|
|
@@ -157,7 +141,7 @@ export function applyPiEvent(state, event) {
|
|
|
157
141
|
}
|
|
158
142
|
if (event.type === "tool_execution_end") {
|
|
159
143
|
const id = typeof event.toolCallId === "string" ? event.toolCallId : "unknown";
|
|
160
|
-
state.blocks.push({ type: "tool_result", tool_use_id: id, content:
|
|
144
|
+
state.blocks.push({ type: "tool_result", tool_use_id: id, content: textContent(event.result), is_error: event.isError === true });
|
|
161
145
|
}
|
|
162
146
|
if (event.type === "message_end" || event.type === "turn_end") {
|
|
163
147
|
const message = asRecord(event.message);
|
|
@@ -207,7 +207,7 @@ function buildStructuredOutputPayload(snapshot) {
|
|
|
207
207
|
return {
|
|
208
208
|
wandProtocolVersion: WAND_PROTOCOL_VERSION,
|
|
209
209
|
output: snapshot.output,
|
|
210
|
-
messages: snapshot.messages ? enrichStructuredMessages(snapshot.messages
|
|
210
|
+
messages: snapshot.messages ? enrichStructuredMessages(snapshot.messages) : undefined,
|
|
211
211
|
queuedMessages: snapshot.queuedMessages,
|
|
212
212
|
queuedMessageSkills: snapshot.queuedMessageSkills,
|
|
213
213
|
sessionKind: "structured",
|
|
@@ -259,7 +259,7 @@ function buildIncrementalStructuredPayload(snapshot, cardDefaults) {
|
|
|
259
259
|
const messages = snapshot.messages ?? [];
|
|
260
260
|
// Derive semantics from the complete current turn before taking the
|
|
261
261
|
// incremental tail; TaskCreate results can live in an earlier provider frame.
|
|
262
|
-
const clientMessages = enrichStructuredMessages(messages
|
|
262
|
+
const clientMessages = enrichStructuredMessages(messages);
|
|
263
263
|
const lastTurn = clientMessages.length > 0 ? clientMessages[clientMessages.length - 1] : undefined;
|
|
264
264
|
// Streaming turn (index 0 here) is preserved verbatim; truncation only kicks
|
|
265
265
|
// in if the live response is already bigger than the transport threshold,
|
|
@@ -5313,7 +5313,7 @@ html:not(.is-wand-app) .input-composer .wand-composer-select-trigger {
|
|
|
5313
5313
|
`),s=0;s<a.length;s++)r+=Aye(a[s]);var u=t.slice(n[0].length),c=r?'<div class="user-attachments">'+r+"</div>":"",d=u.trim()?'<div class="user-attachment-text">'+Oe(u)+"</div>":"";return c+d}function Dye(e){var t={};if(!Array.isArray(e))return t;for(var n=0;n<e.length;n++)for(var r=e[n]&&Array.isArray(e[n].content)?e[n].content:[],a=0;a<r.length;a++){var s=r[a];if(!(!s||s.type!=="tool_result")){var u=s.tool_use_id;u&&(t[u]||(t[u]=[]),t[u].push(s))}}return t}function kye(e,t){if(!e||!e.id||e.id!==U_||!T1e(e,t))return!1;var n=m.sessions.find(function(r){return r.id===m.selectedId});return n?!!(n.structuredState&&n.structuredState.inFlight)&&n.status==="running":!1}function tw(e,t,n,r,a,s){var u=s||{};if(!e||!e.type)return"";switch(e.type){case"text":return t==="assistant"&&e.__processing?'<div class="typing-indicator"><span></span><span></span><span></span></div>':t==="assistant"?VF(e.text||""):G_(e.text||"");case"thinking":var c=e.thinking||"",d=e.thinking===void 0&&e.type==="thinking";if(d)return'<div class="thinking-inline thinking-streaming" data-thinking=""><div class="thinking-streaming-inner"><span class="thinking-streaming-icon spinning">'+jn("spark",{size:12,strokeWidth:1.8})+'</span><div class="thinking-streaming-text"></div></div></div>';if(!c.trim())return"";var f=io("thinking",[a,r]),p=ys(f),g=p===null?Td("thinking"):p,v=g?c:"\u6DF1\u5EA6\u601D\u8003";return'<div class="thinking-inline '+(g?"expanded":"collapsed")+'" data-expand-kind="thinking" data-expand-key="'+Oe(f)+'" data-thinking="'+Oe(c)+'" onclick="__thinkingToggle(this)"><span class="thinking-inline-icon">'+jn("spark",{size:12,strokeWidth:1.8})+'</span><span class="thinking-inline-preview">'+Oe(g?c:v)+'</span><span class="thinking-inline-action">'+(g?"\u6536\u8D77":"\u5C55\u5F00")+"</span></div>";case"tool_use":var b=_F(n,e.id),x=Mye(e,b,r,a,u);return rye(n,e.id)&&(x=aye(e.name||"\u5DE5\u5177")+x),x;case"tool_result":return"";default:var w=e&&e.type?String(e.type):"\u672A\u77E5",E="";try{E=JSON.stringify(e,null,2)}catch{E="{}"}return`<div class="unknown-block collapsed" onclick="this.classList.toggle('collapsed')"><div class="unknown-block-header"><span class="unknown-block-icon">`+jn("question",{size:13,strokeWidth:1.8})+'</span><span class="unknown-block-label">\u672A\u8BC6\u522B\u7684\u5185\u5BB9\u5757\uFF1A'+Oe(w)+'</span><span class="unknown-block-toggle">\u25BC</span></div><pre class="unknown-block-body">'+Oe(E)+"</pre></div>"}}function Bye(e,t,n,r,a,s,u,c){var d=c||{},f=e.id||"tool-"+n,p=io("inline-tool",[s,f||u,u]),g=ys(p),v=e.input||{},b=ql(t&&t.content),x=t&&t.is_error,w=b.length>0,E=x?jn("close",{size:11,strokeWidth:2.2}):w?jn("check",{size:11,strokeWidth:2.2}):"\u2026",C="",k="",D="",F="";if(n==="Read"){C='<svg class="inline-tool-icon" viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1 3.5A1.5 1.5 0 0 1 2.5 2h2.764c.958 0 1.76.56 2.311 1.184C8.405 3.77 9.146 4 10 4h3.5A1.5 1.5 0 0 1 15 5.5v7a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 1 12.5v-9z"/><path d="M2 5.5h12M2 8h8M2 10.5h5"/></svg>';var A=v.file_path||v.path||r||"",T="";v.limit&&(T=" "+v.offset+"-"+(v.offset+v.limit)),k=A,D=T}else if(n==="Glob"){C='<svg class="inline-tool-icon" viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="7" cy="7" r="5"/><path d="M10.5 10.5L14 14"/></svg>';var B=v.pattern||"",j=v.path||r||"";k=B,D=j}else if(n==="Grep"){C='<svg class="inline-tool-icon" viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="6.5" cy="6.5" r="4.5"/><path d="M11.5 11.5L15 15"/></svg>';var B=v.pattern||"",j=v.path||r||"";k=B,D=j,v.context&&(D+=" -C"+v.context)}else if(n==="WebFetch"){C='<svg class="inline-tool-icon" viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6.5"/><path d="M8 1.5v13M1.5 8h13"/></svg>';var R=v.url||"";k=R,D=a||""}else if(n==="WebSearch"){C='<svg class="inline-tool-icon" viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="7" cy="7" r="5"/><path d="M10.5 10.5L14 14"/><path d="M5 7h4M7 5v4"/></svg>';var N=v.query||"";k=N,D=a||""}else n==="TodoRead"?(C='<svg class="inline-tool-icon" viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="2" width="12" height="12" rx="2"/><path d="M5 8l2 2 4-4"/></svg>',k="\u8BFB\u53D6\u5F85\u529E\u5217\u8868",D=a||""):(C='<svg class="inline-tool-icon" viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6.5"/><path d="M8 5v3.5M8 11h.01"/></svg>',k=Zx(n),D=a||"");if(w){var P=b.split(`
|
|
5314
5314
|
`);P.length>10?F=P.slice(0,10).join(`
|
|
5315
5315
|
`)+`
|
|
5316
|
-
\u2026`:F=b}var W=Oe(F),_=b,G="",$=d.forceExpandedToolBodies?!0:g===null?Td("inlineTools"):g;w?G='<div class="inline-tool-expanded" style="display: '+($?"block":"none")+';"><div class="inline-tool-result">'+Y_(b,n)+"</div></div>":x?G='<div class="inline-tool-expanded" style="display: '+($?"block":"none")+';"><div class="inline-tool-result inline-tool-error">'+Oe(b||"\u64CD\u4F5C\u5931\u8D25")+"</div></div>":t||(G='<div class="inline-tool-expanded" style="display: '+($?"block":"none")+';"><div class="inline-tool-loading">\u7B49\u5F85\u54CD\u5E94\u2026</div></div>');var O=t&&t._truncated===!0,L="",q=v.file_path||v.path||r||"";if(q&&hw(q)){var z="/api/file-raw?path="+encodeURIComponent(q);L+='<div class="inline-tool-image" onclick="event.stopPropagation();"><img class="inline-tool-image-thumb" loading="lazy" src="'+z+'" alt="'+Oe(q)+'" data-path="'+Oe(q)+`" onclick="event.stopPropagation(); if(window.__openFilePreview)window.__openFilePreview(this.getAttribute('data-path'));" onerror="var w=this.closest('.inline-tool-image'); if(w)w.style.display='none';" /></div>`}var V=t?nw(t.content):[]
|
|
5316
|
+
\u2026`:F=b}var W=Oe(F),_=b,G="",$=d.forceExpandedToolBodies?!0:g===null?Td("inlineTools"):g;w?G='<div class="inline-tool-expanded" style="display: '+($?"block":"none")+';"><div class="inline-tool-result">'+Y_(b,n)+"</div></div>":x?G='<div class="inline-tool-expanded" style="display: '+($?"block":"none")+';"><div class="inline-tool-result inline-tool-error">'+Oe(b||"\u64CD\u4F5C\u5931\u8D25")+"</div></div>":t||(G='<div class="inline-tool-expanded" style="display: '+($?"block":"none")+';"><div class="inline-tool-loading">\u7B49\u5F85\u54CD\u5E94\u2026</div></div>');var O=t&&t._truncated===!0,L="",q=v.file_path||v.path||r||"";if(q&&hw(q)){var z="/api/file-raw?path="+encodeURIComponent(q);L+='<div class="inline-tool-image" onclick="event.stopPropagation();"><img class="inline-tool-image-thumb" loading="lazy" src="'+z+'" alt="'+Oe(q)+'" data-path="'+Oe(q)+`" onclick="event.stopPropagation(); if(window.__openFilePreview)window.__openFilePreview(this.getAttribute('data-path'));" onerror="var w=this.closest('.inline-tool-image'); if(w)w.style.display='none';" /></div>`}for(var V=t?nw(t.content):[],J=0;J<V.length;J++)L+='<div class="inline-tool-image" onclick="event.stopPropagation();"><img class="inline-tool-image-thumb" loading="lazy" src="'+V[J].src+'" alt="\u5DE5\u5177\u8FD4\u56DE\u56FE\u7247" /></div>';var H=D?'<span class="inline-tool-meta">'+Oe(D)+"</span>":"",K=x?"inline-tool-error-inline":"";$&&(K+=" inline-tool-open");var U=O?'data-truncated="true" data-tool-use-id="'+Oe(e.id||"")+'" ':"";return'<div class="inline-tool '+K+'" data-expand-kind="inline-tool" data-expand-key="'+Oe(p)+'" data-result="'+Oe(_)+'" data-preview="'+W+'" data-status="'+(x?"error":w?"done":"pending")+'" '+U+'onclick="__inlineToolToggle(this)"><div class="inline-tool-row"><span class="inline-tool-status">'+E+"</span>"+C+'<span class="inline-tool-title">'+Oe(k)+"</span>"+H+"</div>"+L+G+"</div>"}function Tye(e,t,n,r,a,s){var u=s||{},c=e.input||{},d=c.command||c.cmd||"",f=ql(t&&t.content),p=e.id||"tool-"+n,g=io("terminal",[r,p||a,a]),v=ys(g),b=t&&t.is_error,x=c.exitCode,w="";t?b?w='<span class="term-status-dot term-error"></span>':x===0||x===void 0?w='<span class="term-status-dot term-success"></span>':w='<span class="term-status-dot term-warn"></span>':w='<span class="term-status-dot term-running"></span>';for(var E=Oe(d),C=f.split(`
|
|
5317
5317
|
`),k="",D=0;D<C.length;D++){var F=C[D];!F&&D===C.length-1||(k+='<div class="term-line">'+Oe(F)+"</div>")}var A="";if(t&&x!==void 0){var T=x===0?"term-exit-success":"term-exit-error";A='<div class="term-exit '+T+'">exit '+x+"</div>"}var B=d.length>80?d.slice(0,77)+"\u2026":d,j=u.forceExpandedToolBodies?!0:v===null?Td("terminal"):v,R=t&&t._truncated===!0,N=R?' data-truncated="true" data-tool-use-id="'+Oe(e.id||"")+'"':"";return'<div class="inline-terminal" data-expand-kind="terminal" data-expand-key="'+Oe(g)+'" data-expanded="'+(j?"true":"false")+'"'+N+'><div class="term-header" role="button" tabindex="0" aria-expanded="'+(j?"true":"false")+`" onclick="__terminalExpand(this)" onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();__terminalExpand(this);}">`+w+'<span class="term-cmd-preview"><span class="term-prompt">$</span> '+Oe(B)+'</span><span class="term-toggle-icon">'+(j?"\u25BC":"\u25B6")+'</span></div><div class="term-body" aria-hidden="'+(j?"false":"true")+'" style="display:'+(j?"block":"none")+';"><div class="term-command"><span class="term-prompt">$</span> '+E+"</div>"+(k?'<div class="term-output">'+k+"</div>":"")+A+"</div></div>"}function nw(e){if(!Array.isArray(e))return[];for(var t=[],n=0;n<e.length;n++){var r=e[n];if(!(!r||typeof r!="object")){var a="";if(r.type==="image"){var s=r.source||{};s.type==="base64"&&s.data?a="data:"+(s.media_type||"image/png")+";base64,"+s.data:typeof s.url=="string"?a=s.url:typeof r.url=="string"&&(a=r.url)}else if(r.type==="image_url"){var u=r.image_url;a=typeof u=="string"?u:u&&u.url||""}a&&t.push({src:a})}}return t}function ql(e){return e?typeof e=="string"?e:Array.isArray(e)?e.map(function(t){if(!t||typeof t!="object")return"";if(t.type==="text"&&typeof t.text=="string")return t.text;if(t.type==="image"||t.type==="image_url")return"";try{return JSON.stringify(t)}catch{return""}}).filter(Boolean).join(`
|
|
5318
5318
|
`):"":""}function jye(e,t,n,r,a,s){var u=s||{},c=e.input||{},d=c.file_path||c.path||"",f=d.split("/").pop()||d,p=e.id||"tool-"+n+"-"+(typeof a=="number"?a:0),g=c.old_string||"",v=c.new_string||c.content||"",b=c.new_content||"",x=c.unified_diff||c.diff||"",w=c.kind||"",E=n==="Write"||n==="MultiEdit",C=t&&t.is_error,k=ql(t&&t.content),D="",F="",A="";x?A='<div class="diff-col diff-col-full"><div class="diff-col-label">Diff</div>'+Rye(x)+"</div>":E?F='<div class="diff-line diff-add">+ '+Oe(b)+"</div>":(g&&(D='<div class="diff-line diff-remove">- '+Oe(g)+"</div>"),v&&(F='<div class="diff-line diff-add">+ '+Oe(v)+"</div>"));var T="",B="";t?C?(T="diff-error",B=k.indexOf("haven't granted")!==-1||k.indexOf("permission")!==-1?"\u7B49\u5F85\u6388\u6743":"\u5931\u8D25"):(T="diff-success",B=w==="add"?"\u5DF2\u65B0\u589E":w==="delete"?"\u5DF2\u5220\u9664":w==="move"?"\u5DF2\u79FB\u52A8":"\u5DF2\u4FEE\u6539"):(T="diff-pending",B="\u6267\u884C\u4E2D");var j=io("diff",[r,p||a,a]),R=ys(j),N=Td("editCards"),P=u.forceExpandedToolBodies?!0:R===null?N:R,W=P?"":" collapsed",_=!A&&D&&F,G=_?"diff-col-half":"diff-col-full",$=A||(_?'<div class="diff-col '+G+'"><div class="diff-col-label">\u65E7</div>'+D+"</div>":"")+'<div class="diff-col '+G+'"><div class="diff-col-label">'+(_?"\u65B0":"")+"</div>"+(F||D||K_(d))+"</div>",O=d?'<button class="diff-open-file" type="button" data-path="'+Oe(d)+`" title="\u6253\u5F00\u6587\u4EF6" onclick="event.stopPropagation(); if(window.__openFilePreview)window.__openFilePreview(this.getAttribute('data-path'));">\u6253\u5F00</button>`:"";return'<div class="inline-diff'+W+'" data-tool-name="'+Oe(n)+'" data-expand-kind="diff" data-expand-key="'+Oe(j)+'" data-tool-use-id="'+Oe(p)+'" data-path="'+Oe(d)+'"><div class="diff-header" role="button" tabindex="0" aria-expanded="'+(P?"true":"false")+`" onclick="__tcToggle(event,this)" onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();__tcToggle(event,this);}"><span class="diff-file-icon"></span><span class="diff-file-name">`+Oe(f)+"</span>"+Z6e(d,"diff-path")+'<span class="diff-status '+T+'">'+B+"</span>"+O+'<span class="diff-toggle">\u25BC</span></div><div class="diff-body" aria-hidden="'+(P?"false":"true")+'"><div class="diff-columns">'+$+"</div></div></div>"}function Rye(e){for(var t=String(e||"").split(`
|
|
5319
5319
|
`),n=600,r="",a=0;a<t.length&&a<n;a++){var s=t[a],u="diff-context";/^@@/.test(s)?u="diff-hunk":/^\+/.test(s)&&!/^\+\+\+/.test(s)?u="diff-add":/^-/.test(s)&&!/^---/.test(s)&&(u="diff-remove"),r+='<div class="diff-line '+u+'">'+Oe(s||" ")+"</div>"}return t.length>n&&(r+='<div class="diff-line diff-context">\u2026\uFF08\u5DF2\u622A\u65AD '+(t.length-n)+" \u884C\uFF09</div>"),r||K_("")}function K_(e){var t=e?"\uFF0C\u53EF\u6253\u5F00\u6587\u4EF6\u67E5\u770B\u5F53\u524D\u5185\u5BB9":"";return'<div class="diff-empty">Codex \u672A\u63D0\u4F9B\u5185\u8054 diff'+t+"\u3002</div>"}function Y_(e,t){return e?'<pre class="inline-tool-result-text" style="max-height: 300px; overflow-y: auto;">'+Oe(e)+"</pre>":'<span class="inline-tool-empty">\u65E0\u8F93\u51FA</span>'}function Mye(e,t,n,r,a){var s=a||{},u=e.name||"unknown",c=e.id||"tool-"+u+"-"+(typeof n=="number"?n:0),d=Nye(u,e.input);if(u==="Read"||u==="Glob"||u==="Grep"||u==="WebFetch"||u==="WebSearch"||u==="TodoRead")return Bye(e,t,u,d,"",r,n,s);if(u==="Bash")return Tye(e,t,u,r,n,s);if(u==="Edit"||u==="Write"||u==="MultiEdit")return jye(e,t,u,r,n,s);var f=e.semantic&&e.semantic.kind==="question_request"?e.semantic.questions:null;if(f||u==="AskUserQuestion"&&e.input&&e.input.questions){var p=f||e.input.questions;if(p&&p.length>0){for(var g=!!t,v=m.askUserSelections[c]||{},b=!!v.submitted,x=g?ql(t.content):"",w=x?x.trim().split(`
|