@x1a0f3n9/dsh-client-ui-tool 0.1.5-rc.3
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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +115 -0
- package/README.zh.md +115 -0
- package/lib/client.js +2394 -0
- package/lib/index.js +6 -0
- package/lib/types/client/apply.d.ts +10 -0
- package/lib/types/client/contract/slots.d.ts +100 -0
- package/lib/types/client/index.d.ts +4 -0
- package/lib/types/client/locale.d.ts +3 -0
- package/lib/types/client/tool/ToolCallTree.d.ts +9 -0
- package/lib/types/client/tool/components/AskQuestionCard.d.ts +11 -0
- package/lib/types/client/tool/components/ToolRow.d.ts +75 -0
- package/lib/types/client/tool/models/ask-question-card-model.d.ts +22 -0
- package/lib/types/client/tool/models/diff-card-model.d.ts +37 -0
- package/lib/types/client/tool/models/image-card-model.d.ts +45 -0
- package/lib/types/client/tool/models/primitive-labels.d.ts +36 -0
- package/lib/types/client/tool/models/raw-tool-call.d.ts +27 -0
- package/lib/types/client/tool/models/read-card-model.d.ts +43 -0
- package/lib/types/client/tool/models/search-card-model.d.ts +23 -0
- package/lib/types/client/tool/models/terminal-card-model.d.ts +87 -0
- package/lib/types/client/tool/models/tool-call-model.d.ts +75 -0
- package/lib/types/client/tool/models/web-card-model.d.ts +14 -0
- package/lib/types/client/tool/toolviews/GenericToolCard.d.ts +7 -0
- package/lib/types/client/tool/toolviews/ask-question-row.d.ts +14 -0
- package/lib/types/client/tool/toolviews/bash-sample.d.ts +14 -0
- package/lib/types/client/tool/toolviews/file-mutation-row.d.ts +16 -0
- package/lib/types/client/tool/toolviews/plan-summary.d.ts +48 -0
- package/lib/types/client/tool/toolviews/read-family-row.d.ts +26 -0
- package/lib/types/client/tool/toolviews/read-image-row.d.ts +24 -0
- package/lib/types/client/tool/toolviews/read-row.d.ts +17 -0
- package/lib/types/client/tool/toolviews/search-row.d.ts +14 -0
- package/lib/types/client/tool/toolviews/todo-row.d.ts +14 -0
- package/lib/types/client/tool/toolviews/web-row.d.ts +14 -0
- package/lib/types/index.d.ts +4 -0
- package/package.json +81 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,2394 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@x1a0f3n9/dsh-client-ui-tool",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
|
+
let react = require("react");
|
|
9
|
+
let _x1a0f3n9_dsh_client_ui_primitives = require("@x1a0f3n9/dsh-client-ui-primitives");
|
|
10
|
+
//#region ../../util/workspace-path/src/index.ts
|
|
11
|
+
/** Whether a path uses a Windows drive or UNC prefix. */
|
|
12
|
+
function isWindowsStylePath(value) {
|
|
13
|
+
return /^[A-Za-z]:[/\\]/.test(value) || value.startsWith("\\\\");
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Whether a path is absolute in either spelling the Host accepts: POSIX (`/a/b`) or Windows drive or UNC.
|
|
17
|
+
* @param path - the path to classify.
|
|
18
|
+
* @returns `true` for an absolute path; `false` for a Workspace-relative one.
|
|
19
|
+
*/
|
|
20
|
+
function isAbsoluteWorkspacePath(path) {
|
|
21
|
+
return path.startsWith("/") || isWindowsStylePath(path);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve a Workspace-relative path into the Host-facing spelling used by path operations.
|
|
25
|
+
* @param cwd - Session Workspace root, when known.
|
|
26
|
+
* @param path - Absolute or Workspace-relative path.
|
|
27
|
+
* @returns an absolute path when a Workspace root is available, otherwise the original path.
|
|
28
|
+
*/
|
|
29
|
+
function resolveWorkspacePath(cwd, path) {
|
|
30
|
+
if (isAbsoluteWorkspacePath(path)) return path;
|
|
31
|
+
if (cwd === void 0 || cwd === "") return path;
|
|
32
|
+
const separator = isWindowsStylePath(cwd) && cwd.includes("\\") ? "\\" : "/";
|
|
33
|
+
return `${cwd.replace(/[/\\]+$/, "")}${separator}${path.replace(/^[/\\]+/, "")}`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Abbreviate a POSIX home directory for display.
|
|
37
|
+
* @param path - Absolute or already-short display path.
|
|
38
|
+
* @param home - Host account home; absent skips abbreviation.
|
|
39
|
+
* @returns `~` or `~/…` for the POSIX home and its descendants, otherwise `path`.
|
|
40
|
+
*/
|
|
41
|
+
function abbreviateHomePath(path, home) {
|
|
42
|
+
if (home === void 0 || home === "") return path;
|
|
43
|
+
if (isWindowsStylePath(path) || isWindowsStylePath(home)) return path;
|
|
44
|
+
const root = home.replace(/\/+$/, "");
|
|
45
|
+
if (root === "" || root === "/") return path;
|
|
46
|
+
if (path.replace(/\/+$/, "") === root) return "~";
|
|
47
|
+
if (path.startsWith(`${root}/`)) return `~${path.slice(root.length)}`;
|
|
48
|
+
return path;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Strip the workspace root from a workspace-rooted absolute path (display only).
|
|
52
|
+
* @param text - the path to shorten.
|
|
53
|
+
* @param cwd - session workspace root; absent or empty leaves the path unchanged.
|
|
54
|
+
* @returns the path relative to the workspace root, or unchanged when it is not rooted there.
|
|
55
|
+
*/
|
|
56
|
+
function relativizeToCwd(text, cwd) {
|
|
57
|
+
if (cwd === void 0 || cwd === "") return text;
|
|
58
|
+
const root = cwd.replace(/[/\\]+$/, "");
|
|
59
|
+
if (text.startsWith(`${root}/`) || text.startsWith(`${root}\\`)) return text.slice(root.length + 1);
|
|
60
|
+
return text;
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region lib/types/client/tool/models/raw-tool-call.js
|
|
64
|
+
const parsedCalls = /* @__PURE__ */ new WeakMap();
|
|
65
|
+
/**
|
|
66
|
+
* Parse the call head paired with one immutable Tool block.
|
|
67
|
+
* @param block - running or settled Tool block.
|
|
68
|
+
* @returns the Tool name and object arguments, or null when the call head or valid JSON object is unavailable.
|
|
69
|
+
*/
|
|
70
|
+
function parsedToolCall(block) {
|
|
71
|
+
const cached = parsedCalls.get(block);
|
|
72
|
+
if (cached !== void 0 || parsedCalls.has(block)) return cached ?? null;
|
|
73
|
+
const call = "kind" in block ? block.call : block;
|
|
74
|
+
if (call === null) {
|
|
75
|
+
parsedCalls.set(block, null);
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
let value;
|
|
79
|
+
try {
|
|
80
|
+
value = JSON.parse(call.argsRaw);
|
|
81
|
+
} catch {
|
|
82
|
+
parsedCalls.set(block, null);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
86
|
+
parsedCalls.set(block, null);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const parsed = {
|
|
90
|
+
name: call.name,
|
|
91
|
+
args: value
|
|
92
|
+
};
|
|
93
|
+
parsedCalls.set(block, parsed);
|
|
94
|
+
return parsed;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Read the exact single text block consumed by first-party card derivations.
|
|
98
|
+
* @param block - settled Tool result.
|
|
99
|
+
* @returns its text, or undefined for any other content layout.
|
|
100
|
+
*/
|
|
101
|
+
function singleResultText(block) {
|
|
102
|
+
if (block.content.length !== 1) return void 0;
|
|
103
|
+
const only = block.content[0];
|
|
104
|
+
return only?.type === "text" ? only.text : void 0;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Validate the optional escalation pair shared by first-party shell and file
|
|
108
|
+
* mutation tools.
|
|
109
|
+
* @param args - parsed open-root Tool arguments.
|
|
110
|
+
* @returns whether the declared escalation fields form a valid pair.
|
|
111
|
+
*/
|
|
112
|
+
function validEscalationFields(args) {
|
|
113
|
+
const permission = args.sandbox_permissions;
|
|
114
|
+
const justification = args.justification;
|
|
115
|
+
if (permission === void 0 && justification === void 0) return true;
|
|
116
|
+
if (permission !== "workspace-write" && permission !== "danger-full-access") return false;
|
|
117
|
+
return typeof justification === "string" && justification.trim() !== "";
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region lib/types/client/tool/models/read-card-model.js
|
|
121
|
+
/** Whether a model-supplied argument is a 1-based line position or count: an integer of at least 1. */
|
|
122
|
+
function positiveInteger$1(value) {
|
|
123
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1;
|
|
124
|
+
}
|
|
125
|
+
function validReadCall(block) {
|
|
126
|
+
const call = parsedToolCall(block);
|
|
127
|
+
if (call?.name !== "read") return false;
|
|
128
|
+
const { file_path: path, offset, limit } = call.args;
|
|
129
|
+
if (typeof path !== "string" || path.trim() === "") return false;
|
|
130
|
+
if (offset !== void 0 && !positiveInteger$1(offset)) return false;
|
|
131
|
+
if (limit !== void 0 && !positiveInteger$1(limit)) return false;
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
function readMeta(meta) {
|
|
135
|
+
if (typeof meta !== "object" || meta === null || Array.isArray(meta)) return null;
|
|
136
|
+
const { path, offset, lines, totalLines, lang } = meta;
|
|
137
|
+
if (typeof path !== "string" || typeof offset !== "number" || !Number.isInteger(offset) || offset < 1) return null;
|
|
138
|
+
if (typeof totalLines !== "number" || !Number.isInteger(totalLines) || totalLines < 0 || !Array.isArray(lines)) return null;
|
|
139
|
+
if (lang !== void 0 && typeof lang !== "string") return null;
|
|
140
|
+
const narrowed = [];
|
|
141
|
+
let previous = offset - 1;
|
|
142
|
+
for (const line of lines) {
|
|
143
|
+
if (typeof line !== "object" || line === null || Array.isArray(line)) return null;
|
|
144
|
+
const { number, text } = line;
|
|
145
|
+
if (typeof number !== "number" || !Number.isInteger(number) || number < 1 || number <= previous) return null;
|
|
146
|
+
if (number > totalLines || typeof text !== "string") return null;
|
|
147
|
+
previous = number;
|
|
148
|
+
narrowed.push({
|
|
149
|
+
number,
|
|
150
|
+
text
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
path,
|
|
155
|
+
offset,
|
|
156
|
+
lines: narrowed,
|
|
157
|
+
totalLines,
|
|
158
|
+
...lang === void 0 ? {} : { lang }
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The line one `read` call was about, from its arguments.
|
|
163
|
+
*
|
|
164
|
+
* `offset` is the read tool's own 1-based start line, so opening the path can
|
|
165
|
+
* land where the model looked. Available while the call is still running,
|
|
166
|
+
* unlike the persisted metadata, because the arguments carry it. The arguments
|
|
167
|
+
* are model-produced JSON: only an integer of at least 1 is a line, and a call
|
|
168
|
+
* whose `offset` is anything else names none.
|
|
169
|
+
* @param block - running or settled Tool block.
|
|
170
|
+
* @returns the 1-based line, or undefined when the call named none.
|
|
171
|
+
*/
|
|
172
|
+
function readCallLine(block) {
|
|
173
|
+
if (!validReadCall(block)) return void 0;
|
|
174
|
+
const { offset } = parsedToolCall(block)?.args ?? {};
|
|
175
|
+
return positiveInteger$1(offset) ? offset : void 0;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Derive a settled root read card after validating its persisted metadata and
|
|
179
|
+
* model-facing read envelope.
|
|
180
|
+
* @param block - running or settled Tool block.
|
|
181
|
+
* @param sessionCwd - the session workspace root; a workspace-rooted absolute
|
|
182
|
+
* path label displays relative to it. Absent leaves the path as authored.
|
|
183
|
+
* @param home - host account home; a leftover POSIX home path displays as `~`.
|
|
184
|
+
* @returns the read-card props, or null for the generic path.
|
|
185
|
+
*/
|
|
186
|
+
function readCardModel(block, sessionCwd, home) {
|
|
187
|
+
if (block.parentCallId !== void 0 || !("kind" in block) || block.isError) return null;
|
|
188
|
+
if (!validReadCall(block)) return null;
|
|
189
|
+
const meta = readMeta(block.meta);
|
|
190
|
+
if (meta === null) return null;
|
|
191
|
+
const text = singleResultText(block);
|
|
192
|
+
if (text === void 0) return null;
|
|
193
|
+
if (/^<path>[^\n]*<\/path>\n<type>file<\/type>\n<content>\n([\s\S]*)\n<\/content>$/u.exec(text)?.[1] === void 0) return null;
|
|
194
|
+
return {
|
|
195
|
+
label: abbreviateHomePath(relativizeToCwd(meta.path, sessionCwd), home),
|
|
196
|
+
lines: meta.lines,
|
|
197
|
+
totalLines: meta.totalLines,
|
|
198
|
+
lang: meta.lang
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region lib/types/client/tool/models/diff-card-model.js
|
|
203
|
+
/**
|
|
204
|
+
* Narrow opaque result metadata's `diffs` to well-formed hunks.
|
|
205
|
+
* @param diffs - the metadata field to validate.
|
|
206
|
+
* @returns the validated hunks, or null when the payload is not usable.
|
|
207
|
+
*/
|
|
208
|
+
function narrowDiffs(diffs) {
|
|
209
|
+
if (!Array.isArray(diffs) || diffs.length === 0) return null;
|
|
210
|
+
const out = [];
|
|
211
|
+
for (const hunk of diffs) {
|
|
212
|
+
if (typeof hunk !== "object" || hunk === null) return null;
|
|
213
|
+
const { path, oldText, newText } = hunk;
|
|
214
|
+
if (typeof path !== "string") return null;
|
|
215
|
+
if (oldText !== null && typeof oldText !== "string") return null;
|
|
216
|
+
if (typeof newText !== "string") return null;
|
|
217
|
+
out.push({
|
|
218
|
+
path,
|
|
219
|
+
oldText,
|
|
220
|
+
newText
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
225
|
+
function intendedDiff(block) {
|
|
226
|
+
const parsed = parsedToolCall(block);
|
|
227
|
+
if (parsed === null) return null;
|
|
228
|
+
if (parsed.name === "str_replace_editor") {
|
|
229
|
+
const { command, path, file_text: fileText, old_str: oldText, new_str: newText } = parsed.args;
|
|
230
|
+
if (typeof path !== "string" || path.trim() === "") return null;
|
|
231
|
+
if (command === "create") {
|
|
232
|
+
if (fileText !== void 0 && typeof fileText !== "string") return null;
|
|
233
|
+
return {
|
|
234
|
+
tool: "str_replace_editor",
|
|
235
|
+
diff: {
|
|
236
|
+
path,
|
|
237
|
+
oldText: null,
|
|
238
|
+
newText: fileText ?? ""
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
if (command === "str_replace") {
|
|
243
|
+
if (oldText !== void 0 && typeof oldText !== "string") return null;
|
|
244
|
+
if (newText !== void 0 && typeof newText !== "string") return null;
|
|
245
|
+
return {
|
|
246
|
+
tool: "str_replace_editor",
|
|
247
|
+
diff: {
|
|
248
|
+
path,
|
|
249
|
+
oldText: oldText ?? null,
|
|
250
|
+
newText: newText ?? ""
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
const { file_path: path } = parsed.args;
|
|
257
|
+
if (typeof path !== "string" || path.trim() === "") return null;
|
|
258
|
+
if (!validEscalationFields(parsed.args)) return null;
|
|
259
|
+
if (parsed.name === "write") {
|
|
260
|
+
const { content } = parsed.args;
|
|
261
|
+
return typeof content === "string" ? {
|
|
262
|
+
tool: "write",
|
|
263
|
+
diff: {
|
|
264
|
+
path,
|
|
265
|
+
oldText: null,
|
|
266
|
+
newText: content
|
|
267
|
+
}
|
|
268
|
+
} : null;
|
|
269
|
+
}
|
|
270
|
+
if (parsed.name !== "edit") return null;
|
|
271
|
+
const { old_string: oldText, new_string: newText, replace_all: replaceAll } = parsed.args;
|
|
272
|
+
if (typeof oldText !== "string" || typeof newText !== "string") return null;
|
|
273
|
+
if (replaceAll !== void 0 && typeof replaceAll !== "boolean") return null;
|
|
274
|
+
return {
|
|
275
|
+
tool: "edit",
|
|
276
|
+
diff: {
|
|
277
|
+
path,
|
|
278
|
+
oldText: oldText || null,
|
|
279
|
+
newText
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function appliedDiffs(meta) {
|
|
284
|
+
if (typeof meta !== "object" || meta === null || Array.isArray(meta)) return null;
|
|
285
|
+
const diffs = meta.diffs;
|
|
286
|
+
if (!Array.isArray(diffs)) return null;
|
|
287
|
+
if (diffs.length === 0) return "empty";
|
|
288
|
+
return narrowDiffs(diffs);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Derive running diffs for root write/edit and `str_replace_editor`
|
|
292
|
+
* create/replace calls, plus applied settled diffs for root write/edit calls.
|
|
293
|
+
* A successful write with valid empty metadata uses its argument-derived
|
|
294
|
+
* whole-file diff, matching create and identical-overwrite presentation;
|
|
295
|
+
* `str_replace_editor` settles through Generic because it has no result view.
|
|
296
|
+
* @param block - running or settled Tool block.
|
|
297
|
+
* @returns the diff-card props, or null for the generic path.
|
|
298
|
+
*/
|
|
299
|
+
function diffCardModel(block) {
|
|
300
|
+
if (block.parentCallId !== void 0) return null;
|
|
301
|
+
const intended = intendedDiff(block);
|
|
302
|
+
if (intended === null) return null;
|
|
303
|
+
if (!("kind" in block)) return { card: { diffs: [intended.diff] } };
|
|
304
|
+
if (intended.tool === "str_replace_editor") return null;
|
|
305
|
+
if (block.isError) return null;
|
|
306
|
+
const applied = appliedDiffs(block.meta);
|
|
307
|
+
if (applied === null || applied === "empty") return intended.tool === "write" ? { card: { diffs: [intended.diff] } } : null;
|
|
308
|
+
return { card: { diffs: applied } };
|
|
309
|
+
}
|
|
310
|
+
//#endregion
|
|
311
|
+
//#region lib/types/client/tool/models/search-card-model.js
|
|
312
|
+
function validSearchCall(block) {
|
|
313
|
+
const call = parsedToolCall(block);
|
|
314
|
+
if (call === null) return null;
|
|
315
|
+
const { pattern, path } = call.args;
|
|
316
|
+
if (typeof pattern !== "string") return null;
|
|
317
|
+
if (call.name === "grep" && pattern === "") return null;
|
|
318
|
+
if (call.name === "glob" && pattern.trim() === "") return null;
|
|
319
|
+
if (call.name !== "grep" && call.name !== "glob") return null;
|
|
320
|
+
if (path !== void 0 && (typeof path !== "string" || path.trim() === "")) return null;
|
|
321
|
+
if (call.name === "grep") {
|
|
322
|
+
const { include } = call.args;
|
|
323
|
+
if (include !== void 0 && (typeof include !== "string" || !validInclude(include))) return null;
|
|
324
|
+
}
|
|
325
|
+
return call.name;
|
|
326
|
+
}
|
|
327
|
+
function validInclude(include) {
|
|
328
|
+
if (include.trim() === "" || include.startsWith("!")) return false;
|
|
329
|
+
let braceDepth = 0;
|
|
330
|
+
for (const character of include) if (character === "{") braceDepth += 1;
|
|
331
|
+
else if (character === "}") braceDepth = Math.max(0, braceDepth - 1);
|
|
332
|
+
else if (character === "," && braceDepth === 0) return false;
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
function searchFiles(value) {
|
|
336
|
+
if (!Array.isArray(value)) return null;
|
|
337
|
+
const files = [];
|
|
338
|
+
for (const file of value) {
|
|
339
|
+
if (typeof file !== "object" || file === null || Array.isArray(file)) return null;
|
|
340
|
+
const { path, matches } = file;
|
|
341
|
+
if (typeof path !== "string" || !Array.isArray(matches)) return null;
|
|
342
|
+
const narrowed = [];
|
|
343
|
+
for (const match of matches) {
|
|
344
|
+
if (typeof match !== "object" || match === null || Array.isArray(match)) return null;
|
|
345
|
+
const { lineNumber, line } = match;
|
|
346
|
+
if (typeof lineNumber !== "number" || !Number.isInteger(lineNumber) || lineNumber < 1) return null;
|
|
347
|
+
if (typeof line !== "string") return null;
|
|
348
|
+
narrowed.push({
|
|
349
|
+
lineNumber,
|
|
350
|
+
line
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
files.push({
|
|
354
|
+
path,
|
|
355
|
+
matches: narrowed
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
return files;
|
|
359
|
+
}
|
|
360
|
+
function flattenContent(content) {
|
|
361
|
+
const text = content.filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n");
|
|
362
|
+
return text === "" ? void 0 : text;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Derive a settled root grep/glob card from persisted metadata.
|
|
366
|
+
* @param block - running or settled Tool block.
|
|
367
|
+
* @returns search-card props, or null for the generic path.
|
|
368
|
+
*/
|
|
369
|
+
function searchCardModel(block) {
|
|
370
|
+
if (block.parentCallId !== void 0 || !("kind" in block) || block.isError) return null;
|
|
371
|
+
const tool = validSearchCall(block);
|
|
372
|
+
if (tool === null) return null;
|
|
373
|
+
if (typeof block.meta !== "object" || block.meta === null || Array.isArray(block.meta)) return null;
|
|
374
|
+
const meta = block.meta;
|
|
375
|
+
if (typeof meta.truncated !== "boolean") return null;
|
|
376
|
+
if (typeof meta.total !== "number" || !Number.isInteger(meta.total) || meta.total < 0) return null;
|
|
377
|
+
const common = {
|
|
378
|
+
truncated: meta.truncated,
|
|
379
|
+
total: meta.total
|
|
380
|
+
};
|
|
381
|
+
const recovery = meta.truncated ? flattenContent(block.content) : void 0;
|
|
382
|
+
if (tool === "grep") {
|
|
383
|
+
if (meta.shape !== "matches") return null;
|
|
384
|
+
const files = searchFiles(meta.files);
|
|
385
|
+
return files === null ? null : {
|
|
386
|
+
recovery,
|
|
387
|
+
card: {
|
|
388
|
+
kind: "matches",
|
|
389
|
+
files,
|
|
390
|
+
...common
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
if (meta.shape !== "paths" || !Array.isArray(meta.paths)) return null;
|
|
395
|
+
if (!meta.paths.every((path) => typeof path === "string")) return null;
|
|
396
|
+
return {
|
|
397
|
+
recovery,
|
|
398
|
+
card: {
|
|
399
|
+
kind: "paths",
|
|
400
|
+
paths: [...meta.paths],
|
|
401
|
+
...common
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
new TextEncoder();
|
|
406
|
+
new TextDecoder();
|
|
407
|
+
/**
|
|
408
|
+
* Standardized, false-precision-safe wording for one {@link Omitted} value —
|
|
409
|
+
* the "may standardize omission wording" half the library owns. `exact` prints
|
|
410
|
+
* the count (`Omitted 3 items`); `unknown` prints NO count because the caller
|
|
411
|
+
* did not provide one. `none` is the empty string.
|
|
412
|
+
*
|
|
413
|
+
* @param omitted The omission metadata from a retainer result.
|
|
414
|
+
* @param unit The noun for the omitted quantity (`items`, `bytes`, `chars`, `lines`).
|
|
415
|
+
* @returns A neutral clause (no trailing space), or `''` when nothing was omitted.
|
|
416
|
+
*/
|
|
417
|
+
function describeOmitted(omitted, unit) {
|
|
418
|
+
switch (omitted.kind) {
|
|
419
|
+
case "none": return "";
|
|
420
|
+
case "exact": return `Omitted ${omitted.count} ${unit}.`;
|
|
421
|
+
case "unknown": return `More ${unit} were omitted.`;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
//#endregion
|
|
425
|
+
//#region ../../spill/spill-policy/src/notice.ts
|
|
426
|
+
/** Browser-safe formatting and recognition of persisted spill-policy notices. */
|
|
427
|
+
const OPEN = "(";
|
|
428
|
+
const CLOSE = ")";
|
|
429
|
+
const LOCATION = " Full formatted result stored at: ";
|
|
430
|
+
const GUIDANCE_SEPARATOR = ". ";
|
|
431
|
+
const SEPARATOR = "\n\n";
|
|
432
|
+
const EXACT_OMISSION = describeOmitted({
|
|
433
|
+
kind: "exact",
|
|
434
|
+
count: 0
|
|
435
|
+
}, "bytes");
|
|
436
|
+
const COUNT_OFFSET = EXACT_OMISSION.indexOf("0");
|
|
437
|
+
const COUNT_SUFFIX = EXACT_OMISSION.slice(COUNT_OFFSET + 1);
|
|
438
|
+
function isOmission(text) {
|
|
439
|
+
if (text === describeOmitted({ kind: "none" }, "bytes") || text === describeOmitted({ kind: "unknown" }, "bytes")) return true;
|
|
440
|
+
const count = Number(text.slice(COUNT_OFFSET, text.length - COUNT_SUFFIX.length));
|
|
441
|
+
return Number.isSafeInteger(count) && count >= 0 && text === describeOmitted({
|
|
442
|
+
kind: "exact",
|
|
443
|
+
count
|
|
444
|
+
}, "bytes");
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Recognize a final spill-policy notice in persisted text, including notice-only output.
|
|
448
|
+
* This identifies the text convention, not authenticated provenance of tool output.
|
|
449
|
+
* @param text - complete recorded text result.
|
|
450
|
+
* @returns whether a complete notice occupies the end of the result.
|
|
451
|
+
*/
|
|
452
|
+
function hasSpillNotice(text) {
|
|
453
|
+
if (!text.endsWith(CLOSE)) return false;
|
|
454
|
+
let start = 0;
|
|
455
|
+
while (true) {
|
|
456
|
+
const next = text.indexOf(`${SEPARATOR}${OPEN}`, start);
|
|
457
|
+
const candidate = text.slice(start, next < 0 ? -1 : next);
|
|
458
|
+
const location = candidate.indexOf(LOCATION, 1);
|
|
459
|
+
if (candidate.startsWith(OPEN) && location >= 0 && isOmission(candidate.slice(1, location))) return text.indexOf(GUIDANCE_SEPARATOR, start + location + 34) >= 0;
|
|
460
|
+
if (next < 0) return false;
|
|
461
|
+
start = next + 2;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
//#endregion
|
|
465
|
+
//#region lib/types/client/tool/models/terminal-card-model.js
|
|
466
|
+
/**
|
|
467
|
+
* Build the TerminalBlock display copy from the conversation locale seat —
|
|
468
|
+
* the one place the primitive's label surface pairs with this package's
|
|
469
|
+
* dictionary, shared by every terminal render site (chat row, bash row,
|
|
470
|
+
* details panel).
|
|
471
|
+
* @param t - the render site's conversation locale seat.
|
|
472
|
+
* @returns the full label set for {@link TerminalBlockProps}'s `labels`.
|
|
473
|
+
*/
|
|
474
|
+
function terminalBlockLabels(t) {
|
|
475
|
+
return {
|
|
476
|
+
signal: (signal) => t("terminal.signal", { signal }),
|
|
477
|
+
exitCode: (code) => t("terminal.exitCode", { code }),
|
|
478
|
+
running: t("terminal.running"),
|
|
479
|
+
failed: t("terminal.failed"),
|
|
480
|
+
done: t("terminal.done"),
|
|
481
|
+
copy: t("copy"),
|
|
482
|
+
copied: t("copied"),
|
|
483
|
+
noOutput: t("terminal.noOutput"),
|
|
484
|
+
collapseAria: t("terminal.collapseAria"),
|
|
485
|
+
collapse: t("collapse"),
|
|
486
|
+
expandAria: (hidden) => t("terminal.expandAria", { n: hidden }),
|
|
487
|
+
expand: (hidden) => t("terminal.expandRest", { n: hidden })
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Resolve locale-owned `terminal_send` copy while preserving Tool-authored
|
|
492
|
+
* shell commands and descriptions verbatim.
|
|
493
|
+
* @param model - locale-neutral terminal card data.
|
|
494
|
+
* @param t - the render site's conversation locale seat.
|
|
495
|
+
* @returns terminal props and description ready for rendering.
|
|
496
|
+
*/
|
|
497
|
+
function localizeTerminalCardModel(model, t) {
|
|
498
|
+
if (model.copy.kind === "shell") return {
|
|
499
|
+
card: {
|
|
500
|
+
command: model.copy.command,
|
|
501
|
+
...model.card
|
|
502
|
+
},
|
|
503
|
+
description: model.copy.description
|
|
504
|
+
};
|
|
505
|
+
return {
|
|
506
|
+
card: {
|
|
507
|
+
command: model.copy.text === "" ? t("terminal.sendInput") : model.copy.text,
|
|
508
|
+
...model.card
|
|
509
|
+
},
|
|
510
|
+
description: t("terminal.session", { sessionId: model.copy.sessionId })
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* True when a settled terminal card reports a failing exit — a non-zero code
|
|
515
|
+
* or a terminating signal. The bash tool settles a failing command as a
|
|
516
|
+
* completed call (`isError` stays false: the exit status is result data), so
|
|
517
|
+
* this is the collapsed row's only failure signal; without it the red exit
|
|
518
|
+
* pill would be visible only after expanding the card.
|
|
519
|
+
* @param model - a derived terminal card.
|
|
520
|
+
* @returns whether the card's exit status is a failure.
|
|
521
|
+
*/
|
|
522
|
+
function terminalFailed(model) {
|
|
523
|
+
const { exitCode, signal, running } = model.card;
|
|
524
|
+
return running !== true && (exitCode !== void 0 && exitCode !== 0 || signal !== void 0);
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Resolve a shell call's workdir for display: an absolute path is used as-is,
|
|
528
|
+
* a relative one joins under the session workspace, and an omitted one is the
|
|
529
|
+
* session workspace. Without a session cwd, a relative path stays as authored
|
|
530
|
+
* and an omitted one stays absent.
|
|
531
|
+
* @param workdir - the raw call's workdir, if any.
|
|
532
|
+
* @param sessionCwd - the session workspace root, if the caller knows it.
|
|
533
|
+
* @returns the working directory for the prompt label, or undefined.
|
|
534
|
+
*/
|
|
535
|
+
function resolveTerminalCwd(workdir, sessionCwd) {
|
|
536
|
+
if (workdir === void 0 || workdir === "") return sessionCwd;
|
|
537
|
+
if (sessionCwd === void 0 || sessionCwd === "") return normalizeSegments(workdir);
|
|
538
|
+
return normalizeSegments(resolveWorkspacePath(sessionCwd, workdir));
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Collapse `.` and `..` segments so the prompt label names the directory the
|
|
542
|
+
* command actually ran in. The bash executor resolves the workdir before
|
|
543
|
+
* running, so a joined `/w/app/..` must display as `w`, not as `..`. Separators
|
|
544
|
+
* are preserved as authored (a Windows path keeps its backslashes) because this
|
|
545
|
+
* value is only ever displayed; a `..` that would climb past the root is
|
|
546
|
+
* dropped, which is what a filesystem does with it. A UNC path's `server` and
|
|
547
|
+
* `share` are part of its root, not poppable segments: Windows cannot climb
|
|
548
|
+
* above a share, so `\\\\server\\share` with a `..` stays there.
|
|
549
|
+
* @param path - a joined or absolute path, possibly carrying `.`/`..` segments.
|
|
550
|
+
* @returns the same path with those segments resolved.
|
|
551
|
+
*/
|
|
552
|
+
function normalizeSegments(path) {
|
|
553
|
+
if (!/(?:^|[/\\])\.\.?(?:[/\\]|$)/.test(path)) return path;
|
|
554
|
+
const unc = /^[/\\]{2}([^/\\]+)[/\\]+([^/\\]+)/.exec(path);
|
|
555
|
+
if (unc !== null) {
|
|
556
|
+
const [matched, server, share] = unc;
|
|
557
|
+
const root = `\\\\${String(server)}\\${String(share)}`;
|
|
558
|
+
const rest = collapse(path.slice(matched.length), true);
|
|
559
|
+
return rest === "" ? root : `${root}\\${rest}`;
|
|
560
|
+
}
|
|
561
|
+
const separator = path.includes("\\") && !path.includes("/") ? "\\" : "/";
|
|
562
|
+
const rooted = /^[/\\]/.test(path);
|
|
563
|
+
const drive = /^[A-Za-z]:/.exec(path)?.[0] ?? "";
|
|
564
|
+
const body = collapse(path.slice(drive.length), rooted || drive !== "", separator);
|
|
565
|
+
const leading = rooted ? separator : "";
|
|
566
|
+
return drive === "" ? `${leading}${body}` : `${drive}${rooted ? leading : separator}${body}`;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Collapse the `.`/`..` segments of a path body against a known root state.
|
|
570
|
+
* @param body - the path after any drive letter or UNC root.
|
|
571
|
+
* @param rooted - the body hangs off a root, so a `..` at its top is dropped
|
|
572
|
+
* the way a filesystem drops one; without a root the `..` is kept, since it
|
|
573
|
+
* stays meaningful against a cwd this function cannot see.
|
|
574
|
+
* @param separator - separator to rejoin with (default `/`).
|
|
575
|
+
* @returns the collapsed body, without leading or trailing separators.
|
|
576
|
+
*/
|
|
577
|
+
function collapse(body, rooted, separator = "/") {
|
|
578
|
+
const kept = [];
|
|
579
|
+
for (const segment of body.split(/[/\\]/)) {
|
|
580
|
+
if (segment === "" || segment === ".") continue;
|
|
581
|
+
if (segment === "..") {
|
|
582
|
+
if (kept.length > 0 && kept[kept.length - 1] !== "..") kept.pop();
|
|
583
|
+
else if (!rooted) kept.push(segment);
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
kept.push(segment);
|
|
587
|
+
}
|
|
588
|
+
return kept.join(separator);
|
|
589
|
+
}
|
|
590
|
+
function shellCall(name, args) {
|
|
591
|
+
if (name !== "bash" && name !== "pwsh") return null;
|
|
592
|
+
const { command, description, timeoutMs, workdir, run_in_background: background } = args;
|
|
593
|
+
if (typeof command !== "string" || command.trim() === "") return null;
|
|
594
|
+
if (timeoutMs !== void 0 && (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs) || timeoutMs <= 0)) return null;
|
|
595
|
+
if (workdir !== void 0 && typeof workdir !== "string") return null;
|
|
596
|
+
if (background !== void 0 && typeof background !== "boolean") return null;
|
|
597
|
+
if (!validEscalationFields(args)) return null;
|
|
598
|
+
if (description === void 0) return {
|
|
599
|
+
kind: "shell",
|
|
600
|
+
command,
|
|
601
|
+
description: void 0,
|
|
602
|
+
workdir: void 0,
|
|
603
|
+
persistent: true,
|
|
604
|
+
background: false
|
|
605
|
+
};
|
|
606
|
+
if (typeof description !== "string" || description.trim() === "") return null;
|
|
607
|
+
return {
|
|
608
|
+
kind: "shell",
|
|
609
|
+
command,
|
|
610
|
+
description,
|
|
611
|
+
workdir,
|
|
612
|
+
persistent: false,
|
|
613
|
+
background: background === true
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Identify a settled root call from the persistent Bash or PowerShell tool.
|
|
618
|
+
* Its result stays on the generic input/output path because the persistent
|
|
619
|
+
* shell can report resets and partial output without one process exit status.
|
|
620
|
+
* @param block - running or settled Tool block.
|
|
621
|
+
* @returns whether the block is a settled persistent-shell call.
|
|
622
|
+
*/
|
|
623
|
+
function isSettledPersistentShellCall(block) {
|
|
624
|
+
if (!("kind" in block) || block.parentCallId !== void 0) return false;
|
|
625
|
+
const parsed = parsedToolCall(block);
|
|
626
|
+
if (parsed === null) return false;
|
|
627
|
+
return shellCall(parsed.name, parsed.args)?.persistent === true;
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Identify a settled foreground shell preview whose spill footer can hide the exit marker.
|
|
631
|
+
* @param block - running or settled Tool block.
|
|
632
|
+
* @returns whether the shell output must remain generic without an inferred exit status.
|
|
633
|
+
*/
|
|
634
|
+
function isSpilledShellCall(block) {
|
|
635
|
+
if (!("kind" in block)) return false;
|
|
636
|
+
const parsed = parsedToolCall(block);
|
|
637
|
+
if (parsed === null) return false;
|
|
638
|
+
const call = shellCall(parsed.name, parsed.args);
|
|
639
|
+
if (call === null || call.background) return false;
|
|
640
|
+
const output = singleResultText(block);
|
|
641
|
+
return output !== void 0 && hasSpillNotice(output);
|
|
642
|
+
}
|
|
643
|
+
function terminalSendCall(name, args) {
|
|
644
|
+
if (name !== "terminal_send") return null;
|
|
645
|
+
const { sessionId, text, submit, run_in_background: background } = args;
|
|
646
|
+
if (typeof sessionId !== "string" || sessionId === "" || typeof text !== "string") return null;
|
|
647
|
+
if (submit !== void 0 && typeof submit !== "boolean") return null;
|
|
648
|
+
if (background !== void 0 && typeof background !== "boolean") return null;
|
|
649
|
+
return {
|
|
650
|
+
kind: "terminal-send",
|
|
651
|
+
text,
|
|
652
|
+
sessionId,
|
|
653
|
+
background: background === true
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Parse the marker literals owned by `@x1a0f3n9/dsh-shell/render` without
|
|
658
|
+
* importing that Host-only package into the Client dependency graph.
|
|
659
|
+
* @param text - rendered shell result text.
|
|
660
|
+
* @returns output with a trailing exit-code or signal marker extracted.
|
|
661
|
+
*/
|
|
662
|
+
function parseExitStatus(text) {
|
|
663
|
+
const signal = /\n\[killed by signal: ([^\]\n]+)\]$/.exec(text);
|
|
664
|
+
if (signal?.[1] !== void 0) return {
|
|
665
|
+
output: text.slice(0, signal.index),
|
|
666
|
+
signal: signal[1]
|
|
667
|
+
};
|
|
668
|
+
const exit = /\n\[exit code: (\d+)\]$/.exec(text);
|
|
669
|
+
if (exit?.[1] !== void 0) return {
|
|
670
|
+
output: text.slice(0, exit.index),
|
|
671
|
+
exitCode: Number(exit[1])
|
|
672
|
+
};
|
|
673
|
+
return {
|
|
674
|
+
output: text,
|
|
675
|
+
exitCode: 0
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Derive terminal props for supported shell and terminal-send calls, including
|
|
680
|
+
* nested Code Dispatch calls. Standard shell results parse their final status
|
|
681
|
+
* marker; persistent shell results, spill previews, background calls, errors,
|
|
682
|
+
* and malformed input use the generic path. {@link isSettledPersistentShellCall} lets that generic
|
|
683
|
+
* persistent result remain expandable without inventing one process status.
|
|
684
|
+
* @param block - running or settled Tool block.
|
|
685
|
+
* @param sessionCwd - session workspace root used to resolve workdir.
|
|
686
|
+
* @returns locale-neutral terminal-card data, or null for the generic path.
|
|
687
|
+
*/
|
|
688
|
+
function terminalCardModel(block, sessionCwd) {
|
|
689
|
+
const parsed = parsedToolCall(block);
|
|
690
|
+
if (parsed === null) return null;
|
|
691
|
+
const call = shellCall(parsed.name, parsed.args) ?? terminalSendCall(parsed.name, parsed.args);
|
|
692
|
+
if (call === null || call.background) return null;
|
|
693
|
+
const copy = call.kind === "shell" ? {
|
|
694
|
+
kind: "shell",
|
|
695
|
+
command: call.command,
|
|
696
|
+
description: call.description
|
|
697
|
+
} : {
|
|
698
|
+
kind: "terminal-send",
|
|
699
|
+
text: call.text,
|
|
700
|
+
sessionId: call.sessionId
|
|
701
|
+
};
|
|
702
|
+
const cwd = resolveTerminalCwd(call.kind === "shell" ? call.workdir : void 0, sessionCwd);
|
|
703
|
+
if (!("kind" in block)) return {
|
|
704
|
+
copy,
|
|
705
|
+
card: {
|
|
706
|
+
cwd,
|
|
707
|
+
output: void 0,
|
|
708
|
+
exitCode: void 0,
|
|
709
|
+
signal: void 0,
|
|
710
|
+
running: true
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
if (block.isError || call.kind === "shell" && call.persistent || isSpilledShellCall(block)) return null;
|
|
714
|
+
const output = singleResultText(block);
|
|
715
|
+
if (output === void 0) return null;
|
|
716
|
+
const status = call.kind === "terminal-send" ? { output } : parseExitStatus(output);
|
|
717
|
+
return {
|
|
718
|
+
copy,
|
|
719
|
+
card: {
|
|
720
|
+
cwd,
|
|
721
|
+
output: status.output,
|
|
722
|
+
exitCode: status.exitCode,
|
|
723
|
+
signal: status.signal,
|
|
724
|
+
running: false
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
//#endregion
|
|
729
|
+
//#region lib/types/client/tool/models/web-card-model.js
|
|
730
|
+
function validWebCall(block) {
|
|
731
|
+
const call = parsedToolCall(block);
|
|
732
|
+
if (call === null) return null;
|
|
733
|
+
if (call.name === "web_search") {
|
|
734
|
+
const { queries } = call.args;
|
|
735
|
+
if (!Array.isArray(queries) || queries.length === 0) return null;
|
|
736
|
+
return queries.every((query) => typeof query === "string" && query.trim() !== "") ? call.name : null;
|
|
737
|
+
}
|
|
738
|
+
if (call.name === "web_fetch") {
|
|
739
|
+
const { url } = call.args;
|
|
740
|
+
return typeof url === "string" && url.trim() !== "" ? call.name : null;
|
|
741
|
+
}
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
function webSources(value) {
|
|
745
|
+
if (!Array.isArray(value)) return null;
|
|
746
|
+
const sources = [];
|
|
747
|
+
for (const source of value) {
|
|
748
|
+
if (typeof source !== "object" || source === null || Array.isArray(source)) return null;
|
|
749
|
+
const { url, title, snippet, publishedAt } = source;
|
|
750
|
+
if (typeof url !== "string") return null;
|
|
751
|
+
if (title !== void 0 && typeof title !== "string") return null;
|
|
752
|
+
if (snippet !== void 0 && typeof snippet !== "string") return null;
|
|
753
|
+
if (publishedAt !== void 0 && typeof publishedAt !== "string") return null;
|
|
754
|
+
sources.push({
|
|
755
|
+
url,
|
|
756
|
+
...title === void 0 ? {} : { title },
|
|
757
|
+
...snippet === void 0 ? {} : { snippet },
|
|
758
|
+
...publishedAt === void 0 ? {} : { publishedAt }
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
return sources;
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Derive a settled root web-search or web-fetch card from persisted metadata.
|
|
765
|
+
* @param block - running or settled Tool block.
|
|
766
|
+
* @returns web-card props, or null for the generic path.
|
|
767
|
+
*/
|
|
768
|
+
function webCardModel(block) {
|
|
769
|
+
if (block.parentCallId !== void 0 || !("kind" in block) || block.isError) return null;
|
|
770
|
+
const tool = validWebCall(block);
|
|
771
|
+
if (tool === null || typeof block.meta !== "object" || block.meta === null || Array.isArray(block.meta)) return null;
|
|
772
|
+
const meta = block.meta;
|
|
773
|
+
if (typeof meta.truncated !== "boolean") return null;
|
|
774
|
+
if (tool === "web_search") {
|
|
775
|
+
const sources = webSources(meta.sources);
|
|
776
|
+
if (sources === null || meta.answer !== void 0 && typeof meta.answer !== "string") return null;
|
|
777
|
+
return {
|
|
778
|
+
kind: "search",
|
|
779
|
+
answer: meta.answer,
|
|
780
|
+
sources,
|
|
781
|
+
truncated: meta.truncated
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
if (typeof meta.url !== "string") return null;
|
|
785
|
+
if (typeof meta.statusCode !== "number" || !Number.isInteger(meta.statusCode)) return null;
|
|
786
|
+
return {
|
|
787
|
+
kind: "fetch",
|
|
788
|
+
url: meta.url,
|
|
789
|
+
statusCode: meta.statusCode,
|
|
790
|
+
truncated: meta.truncated
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
//#endregion
|
|
794
|
+
//#region lib/types/client/tool/models/tool-call-model.js
|
|
795
|
+
/** Locale key per generic row variant. */
|
|
796
|
+
const VARIANT_TITLE_KEYS = {
|
|
797
|
+
search: "tool.title.search",
|
|
798
|
+
read: "tool.title.read",
|
|
799
|
+
bash: "tool.title.bash",
|
|
800
|
+
write: "tool.title.write",
|
|
801
|
+
edit: "tool.title.edit",
|
|
802
|
+
code: "tool.title.code",
|
|
803
|
+
others: "tool.title.generic"
|
|
804
|
+
};
|
|
805
|
+
/**
|
|
806
|
+
* Known tool name -> variant.
|
|
807
|
+
*
|
|
808
|
+
* `cordis_define` is deliberately absent: ui-cordis registers a keyed
|
|
809
|
+
* `tool.call.toolview` entry for it, and a keyed hit REPLACES the generic row
|
|
810
|
+
* (this table is only reached through GenericToolCard, the dispatch fallback in
|
|
811
|
+
* ToolCallTree). An entry here would be unreachable, and a second title for the
|
|
812
|
+
* same call would be a second answer to a question the card already owns.
|
|
813
|
+
*/
|
|
814
|
+
const TOOL_VARIANTS = {
|
|
815
|
+
bash: "bash",
|
|
816
|
+
pwsh: "bash",
|
|
817
|
+
read: "read",
|
|
818
|
+
read_image: "read",
|
|
819
|
+
web_fetch: "read",
|
|
820
|
+
web_search: "search",
|
|
821
|
+
grep: "search",
|
|
822
|
+
glob: "search",
|
|
823
|
+
write: "write",
|
|
824
|
+
edit: "edit",
|
|
825
|
+
run_code: "code",
|
|
826
|
+
cordis_package_inspect: "read",
|
|
827
|
+
cordis_runtime_inspect: "read",
|
|
828
|
+
cordis_run: "others",
|
|
829
|
+
cordis_stop: "others",
|
|
830
|
+
cordis_undefine: "others"
|
|
831
|
+
};
|
|
832
|
+
/** Tool-owned titles that refine a generic row variant without replacing it. */
|
|
833
|
+
const TOOL_TITLE_KEYS = {
|
|
834
|
+
cordis_package_inspect: "tool.title.inspect",
|
|
835
|
+
cordis_runtime_inspect: "tool.title.inspect",
|
|
836
|
+
cordis_run: "tool.title.runCordis",
|
|
837
|
+
cordis_stop: "tool.title.stopCordis",
|
|
838
|
+
cordis_undefine: "tool.title.removeCordis",
|
|
839
|
+
pwsh: "tool.title.pwsh",
|
|
840
|
+
read_image: "tool.title.readImage"
|
|
841
|
+
};
|
|
842
|
+
/**
|
|
843
|
+
* Classify a tool name into its row variant.
|
|
844
|
+
* @param toolName - wire tool name.
|
|
845
|
+
* @returns matching variant, others when unknown.
|
|
846
|
+
*/
|
|
847
|
+
function classifyTool(toolName) {
|
|
848
|
+
return TOOL_VARIANTS[toolName] ?? "others";
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Flatten a settled result's content blocks to display text: text blocks
|
|
852
|
+
* verbatim, other block shapes as pretty JSON. Empty content on a failed call
|
|
853
|
+
* falls back to the structured error's `name: code` line.
|
|
854
|
+
* @param node - the settled result node.
|
|
855
|
+
* @returns the flattened result text (may be empty).
|
|
856
|
+
*/
|
|
857
|
+
function resultText(node) {
|
|
858
|
+
const parts = [];
|
|
859
|
+
for (const block of node.content) if (block.type === "text") parts.push(block.text);
|
|
860
|
+
else parts.push(JSON.stringify(block, null, 2));
|
|
861
|
+
if (parts.length === 0 && node.error !== void 0) parts.push(`${node.error.name}: ${node.error.code}`);
|
|
862
|
+
return parts.join("\n");
|
|
863
|
+
}
|
|
864
|
+
function parseArgs(argsRaw) {
|
|
865
|
+
try {
|
|
866
|
+
return JSON.parse(argsRaw);
|
|
867
|
+
} catch {
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
function firstLine(text) {
|
|
872
|
+
const nl = text.indexOf("\n");
|
|
873
|
+
return nl === -1 ? text : text.slice(0, nl);
|
|
874
|
+
}
|
|
875
|
+
function pickString(args, keys) {
|
|
876
|
+
for (const key of keys) {
|
|
877
|
+
const v = args[key];
|
|
878
|
+
if (typeof v === "string" && v !== "") return v;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
/** Summary key preference per variant (args-derived; result-derived summaries are a ledger item). */
|
|
882
|
+
const SUMMARY_KEYS = {
|
|
883
|
+
bash: ["description", "command"],
|
|
884
|
+
read: [
|
|
885
|
+
"path",
|
|
886
|
+
"file_path",
|
|
887
|
+
"url"
|
|
888
|
+
],
|
|
889
|
+
search: [
|
|
890
|
+
"query",
|
|
891
|
+
"pattern",
|
|
892
|
+
"url"
|
|
893
|
+
],
|
|
894
|
+
write: ["path", "file_path"],
|
|
895
|
+
edit: ["path", "file_path"],
|
|
896
|
+
code: ["description"],
|
|
897
|
+
others: []
|
|
898
|
+
};
|
|
899
|
+
function deriveSummary(variant, argsRaw) {
|
|
900
|
+
const parsed = parseArgs(argsRaw);
|
|
901
|
+
if (typeof parsed !== "object" || parsed === null) return firstLine(argsRaw);
|
|
902
|
+
const args = parsed;
|
|
903
|
+
if (variant === "search" && Array.isArray(args.queries)) {
|
|
904
|
+
const queries = args.queries.filter((query) => typeof query === "string" && query !== "");
|
|
905
|
+
if (queries.length > 0) return queries.map(firstLine).join(", ");
|
|
906
|
+
}
|
|
907
|
+
const picked = pickString(args, SUMMARY_KEYS[variant]);
|
|
908
|
+
if (picked !== void 0) return firstLine(picked);
|
|
909
|
+
for (const v of Object.values(args)) if (typeof v === "string" && v !== "") return firstLine(v);
|
|
910
|
+
return firstLine(argsRaw);
|
|
911
|
+
}
|
|
912
|
+
/** Path keys only — never `url` (web_fetch lands on the read variant). */
|
|
913
|
+
const FILE_PATH_KEYS = ["path", "file_path"];
|
|
914
|
+
/** File-tool variants whose summary may be an openable workspace path. */
|
|
915
|
+
const FILE_PATH_VARIANTS = new Set([
|
|
916
|
+
"read",
|
|
917
|
+
"write",
|
|
918
|
+
"edit"
|
|
919
|
+
]);
|
|
920
|
+
function deriveFilePath(variant, argsRaw) {
|
|
921
|
+
if (!FILE_PATH_VARIANTS.has(variant)) return void 0;
|
|
922
|
+
const parsed = parseArgs(argsRaw);
|
|
923
|
+
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
924
|
+
const picked = pickString(parsed, FILE_PATH_KEYS);
|
|
925
|
+
return picked === void 0 ? void 0 : firstLine(picked);
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Format one argument payload when its generic input body becomes visible.
|
|
929
|
+
* @param variant - row presentation selected for the Tool name.
|
|
930
|
+
* @param argsRaw - original argument JSON or incomplete raw text.
|
|
931
|
+
* @returns display body, or null for empty input.
|
|
932
|
+
*/
|
|
933
|
+
function formatToolBody(variant, argsRaw) {
|
|
934
|
+
if (argsRaw === "") return null;
|
|
935
|
+
const parsed = parseArgs(argsRaw);
|
|
936
|
+
if (parsed === void 0) return argsRaw;
|
|
937
|
+
if (variant === "code" && typeof parsed === "object" && parsed !== null) {
|
|
938
|
+
const code = parsed.code;
|
|
939
|
+
if (typeof code === "string" && code !== "") return code;
|
|
940
|
+
}
|
|
941
|
+
return JSON.stringify(parsed, null, 2);
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Derive the full row model from a frozen call slice.
|
|
945
|
+
* @param toolName - wire tool name (dispatch-supplied; survives windowless results).
|
|
946
|
+
* @param block - RunningToolCall or ToolResultNode off the snapshot caches.
|
|
947
|
+
* @param cwd - session workspace root; workspace-rooted path summaries display relative to it.
|
|
948
|
+
* @param home - host account home; a leftover POSIX home path displays as `~`.
|
|
949
|
+
* @returns the row model.
|
|
950
|
+
*/
|
|
951
|
+
function toolRowModel(toolName, block, cwd, home) {
|
|
952
|
+
const variant = classifyTool(toolName);
|
|
953
|
+
const done = "kind" in block;
|
|
954
|
+
const argsRaw = (done ? block.call?.argsRaw : block.argsRaw) ?? "";
|
|
955
|
+
const state = !done ? "running" : block.error?.code === "interrupted" ? "stopped" : block.isError ? "error" : "ok";
|
|
956
|
+
const base = argsRaw === "" ? block.callId : abbreviateHomePath(relativizeToCwd(deriveSummary(variant, argsRaw), cwd), home);
|
|
957
|
+
const toolTitleKey = TOOL_TITLE_KEYS[toolName];
|
|
958
|
+
const summary = variant === "others" && toolName !== "" && toolTitleKey === void 0 ? `${toolName} · ${base}` : base;
|
|
959
|
+
const output = done ? resultText(block) || null : null;
|
|
960
|
+
const errorSummary = state === "error" && output !== null ? firstLine(output) : null;
|
|
961
|
+
const bodyRaw = argsRaw === "" ? null : argsRaw;
|
|
962
|
+
return {
|
|
963
|
+
variant,
|
|
964
|
+
titleKey: toolTitleKey ?? VARIANT_TITLE_KEYS[variant],
|
|
965
|
+
summary,
|
|
966
|
+
filePath: deriveFilePath(variant, argsRaw),
|
|
967
|
+
bodyRaw,
|
|
968
|
+
output,
|
|
969
|
+
errorSummary,
|
|
970
|
+
state
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
//#endregion
|
|
974
|
+
//#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
|
|
975
|
+
function r(e) {
|
|
976
|
+
var t, f, n = "";
|
|
977
|
+
if ("string" == typeof e || "number" == typeof e) n += e;
|
|
978
|
+
else if ("object" == typeof e) if (Array.isArray(e)) {
|
|
979
|
+
var o = e.length;
|
|
980
|
+
for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
|
|
981
|
+
} else for (f in e) e[f] && (n && (n += " "), n += f);
|
|
982
|
+
return n;
|
|
983
|
+
}
|
|
984
|
+
function clsx() {
|
|
985
|
+
for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
|
|
986
|
+
return n;
|
|
987
|
+
}
|
|
988
|
+
//#endregion
|
|
989
|
+
//#region lib/types/client/tool/models/primitive-labels.js
|
|
990
|
+
/** Localized copy adapters for Cordis-free UI primitives used by Tool cards. */
|
|
991
|
+
/**
|
|
992
|
+
* Build localized Markdown chrome labels.
|
|
993
|
+
* @param t - Conversation locale seat.
|
|
994
|
+
* @returns Markdown chrome labels.
|
|
995
|
+
*/
|
|
996
|
+
function markdownLabels(t) {
|
|
997
|
+
return {
|
|
998
|
+
code: {
|
|
999
|
+
copyLabel: t("copy"),
|
|
1000
|
+
copiedLabel: t("copied")
|
|
1001
|
+
},
|
|
1002
|
+
footnotes: t("markdown.footnotes")
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Build localized diff-card chrome labels.
|
|
1007
|
+
* @param t - Conversation locale seat.
|
|
1008
|
+
* @returns Diff-card chrome labels.
|
|
1009
|
+
*/
|
|
1010
|
+
function diffBlockLabels(t) {
|
|
1011
|
+
return {
|
|
1012
|
+
copy: t("copy"),
|
|
1013
|
+
copied: t("copied"),
|
|
1014
|
+
collapseAria: t("diff.collapseAria"),
|
|
1015
|
+
expandAria: (count) => t("diff.expandAria", { count }),
|
|
1016
|
+
collapse: t("collapse"),
|
|
1017
|
+
expand: (count) => t("diff.expandRest", { count }),
|
|
1018
|
+
files: (count) => t(count === 1 ? "diff.files.one" : "diff.files.other", { count })
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Build localized read-card chrome labels.
|
|
1023
|
+
* @param t - Conversation locale seat.
|
|
1024
|
+
* @returns Read-card chrome labels.
|
|
1025
|
+
*/
|
|
1026
|
+
function readBlockLabels(t) {
|
|
1027
|
+
return {
|
|
1028
|
+
window: (shown, total) => t("read.window", {
|
|
1029
|
+
shown,
|
|
1030
|
+
total
|
|
1031
|
+
}),
|
|
1032
|
+
copy: t("copy"),
|
|
1033
|
+
copied: t("copied"),
|
|
1034
|
+
collapseAria: t("read.collapseAria"),
|
|
1035
|
+
expandAria: (count) => t("read.expandAria", { count }),
|
|
1036
|
+
collapse: t("collapse"),
|
|
1037
|
+
expand: (count) => t("read.expandRest", { count })
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Build localized search-card chrome labels.
|
|
1042
|
+
* @param t - Conversation locale seat.
|
|
1043
|
+
* @returns Search-card chrome labels.
|
|
1044
|
+
*/
|
|
1045
|
+
function searchBlockLabels(t) {
|
|
1046
|
+
return {
|
|
1047
|
+
pathsSummary: (shown, total, truncated) => t(truncated ? "search.paths.truncated" : "search.paths", {
|
|
1048
|
+
shown,
|
|
1049
|
+
total
|
|
1050
|
+
}),
|
|
1051
|
+
matchesSummary: (shown, total, files, truncated) => t(truncated ? "search.matches.truncated" : "search.matches", {
|
|
1052
|
+
shown,
|
|
1053
|
+
total,
|
|
1054
|
+
files
|
|
1055
|
+
}),
|
|
1056
|
+
copy: t("copy"),
|
|
1057
|
+
copied: t("copied"),
|
|
1058
|
+
noResults: t("search.noResults"),
|
|
1059
|
+
collapseAria: t("search.collapseAria"),
|
|
1060
|
+
expandAria: (count) => t("search.expandAria", { count }),
|
|
1061
|
+
collapse: t("collapse"),
|
|
1062
|
+
expand: (count) => t("search.expandRest", { count })
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Build localized web-card chrome labels.
|
|
1067
|
+
* @param t - Conversation locale seat.
|
|
1068
|
+
* @returns Web-card chrome labels.
|
|
1069
|
+
*/
|
|
1070
|
+
function webBlockLabels(t) {
|
|
1071
|
+
return {
|
|
1072
|
+
noResults: t("web.noResults"),
|
|
1073
|
+
sourcesTruncated: t("web.sourcesTruncated"),
|
|
1074
|
+
http: t("web.http"),
|
|
1075
|
+
contentTruncated: t("web.contentTruncated"),
|
|
1076
|
+
markdown: markdownLabels(t)
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
//#endregion
|
|
1080
|
+
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-tool/src/client/tool/components/AskQuestionCard.module.css.mjs
|
|
1081
|
+
const css$3 = ".fsXYAq_card{border:.5px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);border-radius:12px;flex-direction:column;gap:16px;max-height:360px;margin:4px 0 4px 4px;padding:16px 20px;display:flex;overflow-y:auto}.fsXYAq_item{flex-direction:column;gap:2px;min-width:0;display:flex}.fsXYAq_question,.fsXYAq_answer{white-space:pre-wrap;overflow-wrap:anywhere;font-size:var(--dsh-content-font-size,14px);line-height:calc(24px + var(--dsh-content-font-delta,0px));margin:0}.fsXYAq_question{color:var(--dsw-alias-label-tertiary)}.fsXYAq_answer{color:var(--dsw-alias-label-primary)}.fsXYAq_answerLine{display:block}.fsXYAq_skipped{color:var(--dsw-alias-label-tertiary)}.fsXYAq_verdict{color:var(--dsw-alias-label-primary);font-size:var(--dsh-content-font-size,14px);line-height:calc(24px + var(--dsh-content-font-delta,0px));margin:0}.fsXYAq_questionList{flex-direction:column;gap:8px;margin:0;padding-left:20px;display:flex}.fsXYAq_unansweredQuestion{color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;overflow-wrap:anywhere;font-size:var(--dsh-content-font-size,14px);line-height:calc(24px + var(--dsh-content-font-delta,0px))}";
|
|
1082
|
+
const tagId$3 = "@x1a0f3n9/dsh-client-ui-tool/AskQuestionCard.module.css";
|
|
1083
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
|
|
1084
|
+
const tag = document.createElement("style");
|
|
1085
|
+
tag.dataset.plugin = "@x1a0f3n9/dsh-client-ui-tool";
|
|
1086
|
+
tag.dataset.pluginCss = tagId$3;
|
|
1087
|
+
tag.textContent = css$3;
|
|
1088
|
+
document.head.appendChild(tag);
|
|
1089
|
+
}
|
|
1090
|
+
var AskQuestionCard_module_css_default = {
|
|
1091
|
+
"answer": "fsXYAq_answer",
|
|
1092
|
+
"answerLine": "fsXYAq_answerLine",
|
|
1093
|
+
"card": "fsXYAq_card",
|
|
1094
|
+
"item": "fsXYAq_item",
|
|
1095
|
+
"question": "fsXYAq_question",
|
|
1096
|
+
"questionList": "fsXYAq_questionList",
|
|
1097
|
+
"skipped": "fsXYAq_skipped",
|
|
1098
|
+
"unansweredQuestion": "fsXYAq_unansweredQuestion",
|
|
1099
|
+
"verdict": "fsXYAq_verdict"
|
|
1100
|
+
};
|
|
1101
|
+
//#endregion
|
|
1102
|
+
//#region lib/types/client/tool/components/AskQuestionCard.js
|
|
1103
|
+
/**
|
|
1104
|
+
* Render a validated ask-user transcript from plain card data.
|
|
1105
|
+
* @param props - Localized transcript card data.
|
|
1106
|
+
* @returns the readable answered or unanswered question list.
|
|
1107
|
+
*/
|
|
1108
|
+
function AskQuestionCard({ card }) {
|
|
1109
|
+
if (card.kind === "unanswered") return (0, react_jsx_runtime.jsxs)("div", {
|
|
1110
|
+
className: AskQuestionCard_module_css_default.card,
|
|
1111
|
+
children: [(0, react_jsx_runtime.jsx)("p", {
|
|
1112
|
+
className: AskQuestionCard_module_css_default.verdict,
|
|
1113
|
+
children: card.verdict
|
|
1114
|
+
}), (0, react_jsx_runtime.jsx)("ul", {
|
|
1115
|
+
className: AskQuestionCard_module_css_default.questionList,
|
|
1116
|
+
children: card.questions.map((question) => (0, react_jsx_runtime.jsx)("li", {
|
|
1117
|
+
className: AskQuestionCard_module_css_default.unansweredQuestion,
|
|
1118
|
+
children: question.question
|
|
1119
|
+
}, question.id))
|
|
1120
|
+
})]
|
|
1121
|
+
});
|
|
1122
|
+
return (0, react_jsx_runtime.jsx)("dl", {
|
|
1123
|
+
className: AskQuestionCard_module_css_default.card,
|
|
1124
|
+
children: card.questions.map((question) => (0, react_jsx_runtime.jsxs)("div", {
|
|
1125
|
+
className: AskQuestionCard_module_css_default.item,
|
|
1126
|
+
children: [(0, react_jsx_runtime.jsx)("dt", {
|
|
1127
|
+
className: AskQuestionCard_module_css_default.question,
|
|
1128
|
+
children: question.question
|
|
1129
|
+
}), (0, react_jsx_runtime.jsx)("dd", {
|
|
1130
|
+
className: AskQuestionCard_module_css_default.answer,
|
|
1131
|
+
children: question.answers.length === 0 ? (0, react_jsx_runtime.jsx)("span", {
|
|
1132
|
+
className: AskQuestionCard_module_css_default.skipped,
|
|
1133
|
+
children: card.skippedLabel
|
|
1134
|
+
}) : question.answers.map((answer, index) => (0, react_jsx_runtime.jsx)("span", {
|
|
1135
|
+
className: AskQuestionCard_module_css_default.answerLine,
|
|
1136
|
+
children: answer
|
|
1137
|
+
}, `${question.id}-${String(index)}`))
|
|
1138
|
+
})]
|
|
1139
|
+
}, question.id))
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
//#endregion
|
|
1143
|
+
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-tool/src/client/tool/components/ToolRow.module.css.mjs
|
|
1144
|
+
const css$2 = ".o3BgMG_root{flex-direction:column;display:flex}.o3BgMG_row{position:relative;overflow:hidden}.o3BgMG_root[data-state=running] .o3BgMG_row:after{content:\"\";background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);pointer-events:none;width:300px;animation:2.6s ease-out infinite o3BgMG_dsh-tool-row-sweep;position:absolute;top:0;bottom:0;left:0}@keyframes o3BgMG_dsh-tool-row-sweep{0%{left:-300px}90%,to{left:100%}}.o3BgMG_leading{flex-shrink:0}.o3BgMG_root[data-tool^=cordis_] .o3BgMG_leading,.o3BgMG_root[data-tool^=cordis_] .o3BgMG_title{color:var(--dsw-alias-state-business-primary)}.o3BgMG_root[data-tool^=cordis_] .o3BgMG_title{font-weight:500}.o3BgMG_root[data-tool^=cordis_] .o3BgMG_sep{background:var(--dsw-alias-state-business-primary)}.o3BgMG_chevron{color:var(--dsw-alias-label-secondary)}.o3BgMG_title{font-weight:400}.o3BgMG_sep{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.o3BgMG_summary{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);flex:auto;overflow:hidden}.o3BgMG_summarySuffix{white-space:nowrap;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);flex:none;margin-left:4px}.o3BgMG_diffStat{font-family:var(--ds-font-family-code);font-size:calc(var(--dsh-content-font-size-secondary,13px) - 2px);color:var(--dsw-alias-label-caption);margin-left:10px;transform:translateY(.5px)}.o3BgMG_fileLink{text-overflow:ellipsis;white-space:nowrap;min-width:0;font:inherit;text-align:left;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-secondary);text-decoration:underline dotted;text-decoration-color:var(--dsw-alias-label-tertiary);text-underline-offset:3px;cursor:pointer;background:0 0;border:none;flex:0 auto;margin:0;padding:0;text-decoration-thickness:1px;overflow:hidden}.o3BgMG_fileLink:hover{color:var(--dsw-alias-label-primary);text-decoration-color:currentColor}.o3BgMG_errorSummary{color:var(--dsw-alias-state-error-primary)}.o3BgMG_bodyWrap{flex-direction:column;display:flex}.o3BgMG_inspectButton{border:.5px solid var(--dsw-alias-border-l3);corner-shape:round;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-secondary);cursor:pointer;opacity:0;border-radius:999px;align-self:flex-start;align-items:center;gap:4px;margin:4px 0 2px 4px;padding:2px 8px;font-size:11px;line-height:16px;transition:opacity .1s;display:inline-flex}.o3BgMG_root:hover .o3BgMG_inspectButton,.o3BgMG_inspectButton:focus-visible{opacity:1}.o3BgMG_inspectButton:hover{background:var(--dsw-alias-interactive-bg-hover-solid);color:var(--dsw-alias-label-primary)}.o3BgMG_bodyScroll{max-height:260px;overflow-y:auto}.o3BgMG_ioCard{border:.5px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-markdown-code-block);font:var(--dsw-font-markdown-code-block-small);border-radius:12px;flex-direction:column;margin:4px 0 4px 4px;display:flex}.o3BgMG_ioSection{grid-template-columns:max-content 1fr;align-items:baseline;column-gap:14px;max-height:150px;padding:12px 16px;display:grid;overflow-y:auto}.o3BgMG_ioSection::-webkit-scrollbar-thumb{background-clip:padding-box;border:2px solid #0000;border-radius:6px}.o3BgMG_ioSection::-webkit-scrollbar-track{margin:6px 0}.o3BgMG_ioLabel{color:var(--dsw-alias-label-caption);align-self:start;position:sticky;top:0}.o3BgMG_ioDivider{background:var(--dsw-alias-border-l2);flex:none;height:.5px}.o3BgMG_ioText{white-space:pre-wrap;word-break:break-word;min-width:0;color:var(--dsw-alias-label-secondary)}.o3BgMG_ioText[data-error]{color:var(--dsw-alias-state-error-primary)}.o3BgMG_codeBody,.o3BgMG_terminalBody,.o3BgMG_diffBody,.o3BgMG_readBody,.o3BgMG_imageBody,.o3BgMG_searchBody,.o3BgMG_webBody{margin:4px 0 4px 4px}.o3BgMG_searchRecovery{white-space:pre-wrap;overflow-wrap:anywhere;font:var(--dsw-font-xs-13);color:var(--dsw-alias-label-tertiary);margin:4px 0 4px 4px}.o3BgMG_imageLabel{overflow-wrap:anywhere;font:var(--dsw-font-sm-13);color:var(--dsw-alias-label-secondary);margin-bottom:4px}.o3BgMG_imageMeta{white-space:pre-wrap;overflow-wrap:anywhere;font:var(--dsw-font-xs-13);color:var(--dsw-alias-label-tertiary)}.o3BgMG_codeBody{--dsl-code-block-content-font:var(--dsw-font-markdown-code-block-small)}.o3BgMG_terminalBody{--dsl-terminal-font:var(--dsw-font-markdown-code-block-small);--dsl-terminal-line-height:18px;--dsl-terminal-output-max-height:224px;border:.5px solid var(--dsw-alias-border-l1)}.o3BgMG_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}";
|
|
1145
|
+
const tagId$2 = "@x1a0f3n9/dsh-client-ui-tool/ToolRow.module.css";
|
|
1146
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
1147
|
+
const tag = document.createElement("style");
|
|
1148
|
+
tag.dataset.plugin = "@x1a0f3n9/dsh-client-ui-tool";
|
|
1149
|
+
tag.dataset.pluginCss = tagId$2;
|
|
1150
|
+
tag.textContent = css$2;
|
|
1151
|
+
document.head.appendChild(tag);
|
|
1152
|
+
}
|
|
1153
|
+
var ToolRow_module_css_default = {
|
|
1154
|
+
"bodyScroll": "o3BgMG_bodyScroll",
|
|
1155
|
+
"bodyWrap": "o3BgMG_bodyWrap",
|
|
1156
|
+
"chevron": "o3BgMG_chevron",
|
|
1157
|
+
"codeBody": "o3BgMG_codeBody",
|
|
1158
|
+
"diffBody": "o3BgMG_diffBody",
|
|
1159
|
+
"diffStat": "o3BgMG_diffStat",
|
|
1160
|
+
"dsh-tool-row-sweep": "o3BgMG_dsh-tool-row-sweep",
|
|
1161
|
+
"errorSummary": "o3BgMG_errorSummary",
|
|
1162
|
+
"fileLink": "o3BgMG_fileLink",
|
|
1163
|
+
"imageBody": "o3BgMG_imageBody",
|
|
1164
|
+
"imageLabel": "o3BgMG_imageLabel",
|
|
1165
|
+
"imageMeta": "o3BgMG_imageMeta",
|
|
1166
|
+
"inspectButton": "o3BgMG_inspectButton",
|
|
1167
|
+
"ioCard": "o3BgMG_ioCard",
|
|
1168
|
+
"ioDivider": "o3BgMG_ioDivider",
|
|
1169
|
+
"ioLabel": "o3BgMG_ioLabel",
|
|
1170
|
+
"ioSection": "o3BgMG_ioSection",
|
|
1171
|
+
"ioText": "o3BgMG_ioText",
|
|
1172
|
+
"leading": "o3BgMG_leading",
|
|
1173
|
+
"readBody": "o3BgMG_readBody",
|
|
1174
|
+
"root": "o3BgMG_root",
|
|
1175
|
+
"row": "o3BgMG_row",
|
|
1176
|
+
"searchBody": "o3BgMG_searchBody",
|
|
1177
|
+
"searchRecovery": "o3BgMG_searchRecovery",
|
|
1178
|
+
"sep": "o3BgMG_sep",
|
|
1179
|
+
"summary": "o3BgMG_summary",
|
|
1180
|
+
"summarySuffix": "o3BgMG_summarySuffix",
|
|
1181
|
+
"terminalBody": "o3BgMG_terminalBody",
|
|
1182
|
+
"title": "o3BgMG_title",
|
|
1183
|
+
"visuallyHidden": "o3BgMG_visuallyHidden",
|
|
1184
|
+
"webBody": "o3BgMG_webBody"
|
|
1185
|
+
};
|
|
1186
|
+
//#endregion
|
|
1187
|
+
//#region lib/types/client/tool/components/ToolRow.js
|
|
1188
|
+
function leadingFor$1(state, icon) {
|
|
1189
|
+
switch (state) {
|
|
1190
|
+
case "error": return (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.StateDot, { state: "error" });
|
|
1191
|
+
case "stopped": return (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.StateDot, { state: "warning" });
|
|
1192
|
+
default: return icon;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
/** Visually hidden run-state label: the StateDot and the CSS sweep are both
|
|
1196
|
+
* aria-hidden / colour-only, so assistive technology needs this text to know a
|
|
1197
|
+
* row is running, failed, or interrupted. null in the ok state (the icon and
|
|
1198
|
+
* summary already describe a settled row). */
|
|
1199
|
+
function stateStatus$1(state, t) {
|
|
1200
|
+
switch (state) {
|
|
1201
|
+
case "running": return t("row.running");
|
|
1202
|
+
case "error": return t("row.failed");
|
|
1203
|
+
case "stopped": return t("row.stopped");
|
|
1204
|
+
default: return null;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
function ToolRow({ t, variant, toolName, icon, title, summary, summarySuffix, bodyRaw, output, askQuestion, errorSummary, terminal, diff, read, image, renderSlot, loadImage, search, web, state, filePath, filePathLine, onOpenFile, inspect }) {
|
|
1208
|
+
const [expanded, setExpanded] = (0, react.useState)(false);
|
|
1209
|
+
const terminalLabels = (0, react.useMemo)(() => terminalBlockLabels(t), [t]);
|
|
1210
|
+
const diffLabels = (0, react.useMemo)(() => diffBlockLabels(t), [t]);
|
|
1211
|
+
const readLabels = (0, react.useMemo)(() => readBlockLabels(t), [t]);
|
|
1212
|
+
const searchLabels = (0, react.useMemo)(() => searchBlockLabels(t), [t]);
|
|
1213
|
+
const webLabels = (0, react.useMemo)(() => webBlockLabels(t), [t]);
|
|
1214
|
+
const terminalBody = terminal === void 0 || terminal === null ? null : localizeTerminalCardModel(terminal, t);
|
|
1215
|
+
const diffBody = diff ?? null;
|
|
1216
|
+
const readBody = read ?? null;
|
|
1217
|
+
const imageBody = image !== void 0 && image !== null && renderSlot !== void 0 && loadImage !== void 0 ? image : null;
|
|
1218
|
+
const searchBody = search ?? null;
|
|
1219
|
+
const webBody = web ?? null;
|
|
1220
|
+
const askQuestionBody = askQuestion ?? null;
|
|
1221
|
+
const outputText = output ?? null;
|
|
1222
|
+
const card = askQuestionBody ?? terminalBody ?? diffBody ?? readBody ?? imageBody ?? searchBody ?? webBody;
|
|
1223
|
+
const expandable = bodyRaw != null || outputText !== null || card !== null;
|
|
1224
|
+
const open = expanded && expandable;
|
|
1225
|
+
const bodyText = (0, react.useMemo)(() => open && card === null && bodyRaw != null ? formatToolBody(variant, bodyRaw) : null, [
|
|
1226
|
+
bodyRaw,
|
|
1227
|
+
card,
|
|
1228
|
+
open,
|
|
1229
|
+
variant
|
|
1230
|
+
]);
|
|
1231
|
+
const status = stateStatus$1(state, t);
|
|
1232
|
+
const failureLine = state === "error" ? errorSummary ?? null : null;
|
|
1233
|
+
const summaryText = failureLine ?? terminalBody?.description ?? summary;
|
|
1234
|
+
const diffStat = (0, react.useMemo)(() => {
|
|
1235
|
+
if (diffBody === null) return null;
|
|
1236
|
+
const { added, removed } = (0, _x1a0f3n9_dsh_client_ui_primitives.diffTotals)(diffBody.card.diffs);
|
|
1237
|
+
return `+${added} -${removed}`;
|
|
1238
|
+
}, [diffBody]);
|
|
1239
|
+
const suffix = failureLine === null ? summarySuffix ?? diffStat : null;
|
|
1240
|
+
const fileLink = filePath !== void 0 && onOpenFile !== void 0 && failureLine === null;
|
|
1241
|
+
const toggleExpand = () => {
|
|
1242
|
+
setExpanded((v) => !v);
|
|
1243
|
+
};
|
|
1244
|
+
const openFile = (event) => {
|
|
1245
|
+
event.stopPropagation();
|
|
1246
|
+
if (filePath === void 0 || onOpenFile === void 0) return;
|
|
1247
|
+
if (filePathLine === void 0) onOpenFile(filePath);
|
|
1248
|
+
else onOpenFile(filePath, { line: filePathLine });
|
|
1249
|
+
};
|
|
1250
|
+
const fileLinkKeyDown = (event) => {
|
|
1251
|
+
if (event.key === "Enter" || event.key === " ") event.stopPropagation();
|
|
1252
|
+
};
|
|
1253
|
+
const cardBody = variant === "code" ? null : bodyText;
|
|
1254
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
1255
|
+
className: ToolRow_module_css_default.root,
|
|
1256
|
+
"data-variant": variant,
|
|
1257
|
+
"data-tool": toolName,
|
|
1258
|
+
"data-state": state,
|
|
1259
|
+
children: [status !== null && (0, react_jsx_runtime.jsx)("span", {
|
|
1260
|
+
className: ToolRow_module_css_default.visuallyHidden,
|
|
1261
|
+
children: status
|
|
1262
|
+
}), (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.DisclosureRow, {
|
|
1263
|
+
rowClassName: ToolRow_module_css_default.row,
|
|
1264
|
+
leadingClassName: ToolRow_module_css_default.leading,
|
|
1265
|
+
titleClassName: ToolRow_module_css_default.title,
|
|
1266
|
+
chevronClassName: ToolRow_module_css_default.chevron,
|
|
1267
|
+
icon: leadingFor$1(state, icon),
|
|
1268
|
+
title,
|
|
1269
|
+
open,
|
|
1270
|
+
expandable,
|
|
1271
|
+
expandOnRowClick: true,
|
|
1272
|
+
keepContentWhenOpen: true,
|
|
1273
|
+
onToggle: toggleExpand,
|
|
1274
|
+
collapsedContent: summaryText !== "" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1275
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1276
|
+
className: ToolRow_module_css_default.sep,
|
|
1277
|
+
"aria-hidden": true
|
|
1278
|
+
}),
|
|
1279
|
+
fileLink ? (0, react_jsx_runtime.jsx)("button", {
|
|
1280
|
+
type: "button",
|
|
1281
|
+
className: ToolRow_module_css_default.fileLink,
|
|
1282
|
+
onClick: openFile,
|
|
1283
|
+
onKeyDown: fileLinkKeyDown,
|
|
1284
|
+
children: summaryText
|
|
1285
|
+
}) : (0, react_jsx_runtime.jsx)("span", {
|
|
1286
|
+
className: clsx(ToolRow_module_css_default.summary, failureLine !== null && ToolRow_module_css_default.errorSummary),
|
|
1287
|
+
children: summaryText
|
|
1288
|
+
}),
|
|
1289
|
+
suffix !== null && (0, react_jsx_runtime.jsx)("span", {
|
|
1290
|
+
className: clsx(ToolRow_module_css_default.summarySuffix, suffix === diffStat && ToolRow_module_css_default.diffStat),
|
|
1291
|
+
children: suffix
|
|
1292
|
+
})
|
|
1293
|
+
] }),
|
|
1294
|
+
children: (0, react_jsx_runtime.jsxs)("div", {
|
|
1295
|
+
className: ToolRow_module_css_default.bodyWrap,
|
|
1296
|
+
children: [askQuestionBody !== null ? (0, react_jsx_runtime.jsx)(AskQuestionCard, { card: askQuestionBody }) : terminalBody !== null ? (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.TerminalBlock, {
|
|
1297
|
+
...terminalBody.card,
|
|
1298
|
+
maxLines: Infinity,
|
|
1299
|
+
labels: terminalLabels,
|
|
1300
|
+
className: ToolRow_module_css_default.terminalBody
|
|
1301
|
+
}) : diffBody !== null ? (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.DiffBlock, {
|
|
1302
|
+
...diffBody.card,
|
|
1303
|
+
labels: diffLabels,
|
|
1304
|
+
maxLines: 8,
|
|
1305
|
+
className: ToolRow_module_css_default.diffBody
|
|
1306
|
+
}) : readBody !== null ? (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.ReadBlock, {
|
|
1307
|
+
...readBody,
|
|
1308
|
+
labels: readLabels,
|
|
1309
|
+
maxLines: 8,
|
|
1310
|
+
className: ToolRow_module_css_default.readBody
|
|
1311
|
+
}) : imageBody !== null ? (0, react_jsx_runtime.jsxs)("div", {
|
|
1312
|
+
className: ToolRow_module_css_default.imageBody,
|
|
1313
|
+
children: [
|
|
1314
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
1315
|
+
className: ToolRow_module_css_default.imageLabel,
|
|
1316
|
+
children: imageBody.label
|
|
1317
|
+
}),
|
|
1318
|
+
renderSlot !== void 0 && loadImage !== void 0 && renderSlot("tool.call.images", {
|
|
1319
|
+
images: imageBody.images,
|
|
1320
|
+
loadImage,
|
|
1321
|
+
align: "start"
|
|
1322
|
+
}),
|
|
1323
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
1324
|
+
className: ToolRow_module_css_default.imageMeta,
|
|
1325
|
+
children: imageBody.text
|
|
1326
|
+
})
|
|
1327
|
+
]
|
|
1328
|
+
}) : searchBody !== null ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.SearchBlock, {
|
|
1329
|
+
...searchBody.card,
|
|
1330
|
+
labels: searchLabels,
|
|
1331
|
+
maxLines: 8,
|
|
1332
|
+
className: ToolRow_module_css_default.searchBody
|
|
1333
|
+
}), searchBody.recovery !== void 0 && (0, react_jsx_runtime.jsx)("div", {
|
|
1334
|
+
className: ToolRow_module_css_default.searchRecovery,
|
|
1335
|
+
children: searchBody.recovery
|
|
1336
|
+
})] }) : webBody !== null ? (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.WebBlock, {
|
|
1337
|
+
...webBody,
|
|
1338
|
+
labels: webLabels,
|
|
1339
|
+
className: ToolRow_module_css_default.webBody
|
|
1340
|
+
}) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [variant === "code" && bodyText !== null && (0, react_jsx_runtime.jsx)("div", {
|
|
1341
|
+
className: ToolRow_module_css_default.bodyScroll,
|
|
1342
|
+
children: (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.CodeBlock, {
|
|
1343
|
+
code: bodyText,
|
|
1344
|
+
lang: "typescript",
|
|
1345
|
+
copyLabel: t("copy"),
|
|
1346
|
+
copiedLabel: t("copied"),
|
|
1347
|
+
className: ToolRow_module_css_default.codeBody
|
|
1348
|
+
})
|
|
1349
|
+
}), (cardBody !== null || outputText !== null) && (0, react_jsx_runtime.jsxs)("div", {
|
|
1350
|
+
className: ToolRow_module_css_default.ioCard,
|
|
1351
|
+
children: [
|
|
1352
|
+
cardBody !== null && (0, react_jsx_runtime.jsxs)("div", {
|
|
1353
|
+
className: ToolRow_module_css_default.ioSection,
|
|
1354
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
1355
|
+
className: ToolRow_module_css_default.ioLabel,
|
|
1356
|
+
children: t("row.input")
|
|
1357
|
+
}), (0, react_jsx_runtime.jsx)("span", {
|
|
1358
|
+
className: ToolRow_module_css_default.ioText,
|
|
1359
|
+
children: cardBody
|
|
1360
|
+
})]
|
|
1361
|
+
}),
|
|
1362
|
+
cardBody !== null && outputText !== null && (0, react_jsx_runtime.jsx)("span", {
|
|
1363
|
+
className: ToolRow_module_css_default.ioDivider,
|
|
1364
|
+
"aria-hidden": true
|
|
1365
|
+
}),
|
|
1366
|
+
outputText !== null && (0, react_jsx_runtime.jsxs)("div", {
|
|
1367
|
+
className: ToolRow_module_css_default.ioSection,
|
|
1368
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
1369
|
+
className: ToolRow_module_css_default.ioLabel,
|
|
1370
|
+
children: t("row.output")
|
|
1371
|
+
}), (0, react_jsx_runtime.jsx)("span", {
|
|
1372
|
+
className: ToolRow_module_css_default.ioText,
|
|
1373
|
+
"data-error": state === "error" || void 0,
|
|
1374
|
+
children: outputText
|
|
1375
|
+
})]
|
|
1376
|
+
})
|
|
1377
|
+
]
|
|
1378
|
+
})] }), inspect !== void 0 && (0, react_jsx_runtime.jsxs)("button", {
|
|
1379
|
+
type: "button",
|
|
1380
|
+
className: ToolRow_module_css_default.inspectButton,
|
|
1381
|
+
onClick: inspect,
|
|
1382
|
+
children: [(0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconInspectOutline12, {}), t("row.inspect")]
|
|
1383
|
+
})]
|
|
1384
|
+
})
|
|
1385
|
+
})]
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
//#endregion
|
|
1389
|
+
//#region lib/types/client/tool/toolviews/GenericToolCard.js
|
|
1390
|
+
/** Variant leading icons (figma table); all glyphs render at 14 inside the 16px leading box. */
|
|
1391
|
+
const VARIANT_ICONS = {
|
|
1392
|
+
search: (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconSearchOutline16, { size: 14 }),
|
|
1393
|
+
read: (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconBrowseOutline16, { size: 14 }),
|
|
1394
|
+
bash: (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconApiOutline14, { size: 14 }),
|
|
1395
|
+
write: (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconEditOutline16, { size: 14 }),
|
|
1396
|
+
edit: (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconEditOutline16, { size: 14 }),
|
|
1397
|
+
code: (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconCodeOutline16, { size: 14 }),
|
|
1398
|
+
others: (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconSparkle16, { size: 14 })
|
|
1399
|
+
};
|
|
1400
|
+
function GenericToolCard({ toolName, block, cwd, home, openFile, inspect, t }) {
|
|
1401
|
+
const model = toolRowModel(toolName, block, cwd, home);
|
|
1402
|
+
const terminal = terminalCardModel(block, cwd);
|
|
1403
|
+
const read = readCardModel(block, cwd, home);
|
|
1404
|
+
const diff = diffCardModel(block);
|
|
1405
|
+
const search = searchCardModel(block);
|
|
1406
|
+
const web = webCardModel(block);
|
|
1407
|
+
const state = model.state === "ok" && terminal !== null && terminalFailed(terminal) ? "error" : model.state;
|
|
1408
|
+
const singleFile = model.filePath !== void 0;
|
|
1409
|
+
return (0, react_jsx_runtime.jsx)(ToolRow, {
|
|
1410
|
+
t,
|
|
1411
|
+
variant: model.variant,
|
|
1412
|
+
toolName,
|
|
1413
|
+
icon: VARIANT_ICONS[model.variant],
|
|
1414
|
+
title: t(model.titleKey),
|
|
1415
|
+
summary: model.summary,
|
|
1416
|
+
bodyRaw: singleFile ? null : model.bodyRaw,
|
|
1417
|
+
output: model.output,
|
|
1418
|
+
errorSummary: model.errorSummary,
|
|
1419
|
+
terminal,
|
|
1420
|
+
diff,
|
|
1421
|
+
read,
|
|
1422
|
+
search,
|
|
1423
|
+
web,
|
|
1424
|
+
state,
|
|
1425
|
+
filePath: model.filePath,
|
|
1426
|
+
onOpenFile: singleFile ? openFile : void 0,
|
|
1427
|
+
inspect
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
//#endregion
|
|
1431
|
+
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-tool/src/client/tool/ToolCallTree.module.css.mjs
|
|
1432
|
+
const css$1 = ".ztWv_q_callRow{border-radius:6px}.ztWv_q_subCalls{border-left:.5px solid var(--dsw-alias-border-l2);flex-direction:column;gap:4px;margin:4px 0 2px 22px;padding-left:8px;display:flex}";
|
|
1433
|
+
const tagId$1 = "@x1a0f3n9/dsh-client-ui-tool/ToolCallTree.module.css";
|
|
1434
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
1435
|
+
const tag = document.createElement("style");
|
|
1436
|
+
tag.dataset.plugin = "@x1a0f3n9/dsh-client-ui-tool";
|
|
1437
|
+
tag.dataset.pluginCss = tagId$1;
|
|
1438
|
+
tag.textContent = css$1;
|
|
1439
|
+
document.head.appendChild(tag);
|
|
1440
|
+
}
|
|
1441
|
+
var ToolCallTree_module_css_default = {
|
|
1442
|
+
"callRow": "ztWv_q_callRow",
|
|
1443
|
+
"subCalls": "ztWv_q_subCalls"
|
|
1444
|
+
};
|
|
1445
|
+
//#endregion
|
|
1446
|
+
//#region lib/types/client/tool/ToolCallTree.js
|
|
1447
|
+
/** Root/subcall Tool composition with one keyed atomic dispatch path. */
|
|
1448
|
+
/** Resolve a Tool call's wire name from either lifecycle form. */
|
|
1449
|
+
function callName(node) {
|
|
1450
|
+
return "kind" in node ? node.call?.name ?? "" : node.name;
|
|
1451
|
+
}
|
|
1452
|
+
/** One atomic call dispatched through the Tool-owned keyed slot. */
|
|
1453
|
+
const ToolCall = (0, react.memo)(function ToolCall({ renderSlot, callId, toolName, block, openFile, cwd, home, inspectCall, loadImage, t, children }) {
|
|
1454
|
+
const owner = (0, react.useMemo)(() => ({
|
|
1455
|
+
callId,
|
|
1456
|
+
toolName,
|
|
1457
|
+
block,
|
|
1458
|
+
openFile,
|
|
1459
|
+
cwd,
|
|
1460
|
+
home,
|
|
1461
|
+
loadImage,
|
|
1462
|
+
inspect: () => {
|
|
1463
|
+
inspectCall(callId);
|
|
1464
|
+
}
|
|
1465
|
+
}), [
|
|
1466
|
+
callId,
|
|
1467
|
+
toolName,
|
|
1468
|
+
block,
|
|
1469
|
+
openFile,
|
|
1470
|
+
cwd,
|
|
1471
|
+
home,
|
|
1472
|
+
loadImage,
|
|
1473
|
+
inspectCall
|
|
1474
|
+
]);
|
|
1475
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
1476
|
+
className: ToolCallTree_module_css_default.callRow,
|
|
1477
|
+
"data-chat-anchor-key": `call:${callId}`,
|
|
1478
|
+
"data-chat-call-id": callId,
|
|
1479
|
+
children: [renderSlot("tool.call.toolview", owner, {
|
|
1480
|
+
entryKey: toolName,
|
|
1481
|
+
fallback: (0, react_jsx_runtime.jsx)(GenericToolCard, {
|
|
1482
|
+
...owner,
|
|
1483
|
+
t
|
|
1484
|
+
})
|
|
1485
|
+
}), children]
|
|
1486
|
+
});
|
|
1487
|
+
});
|
|
1488
|
+
const ToolCallBranch = (0, react.memo)(function ToolCallBranch({ renderSlot, block, cwd, home, openFile, inspectCall, loadImage, t }) {
|
|
1489
|
+
return (0, react_jsx_runtime.jsx)(ToolCall, {
|
|
1490
|
+
renderSlot,
|
|
1491
|
+
callId: block.callId,
|
|
1492
|
+
toolName: callName(block),
|
|
1493
|
+
block,
|
|
1494
|
+
openFile,
|
|
1495
|
+
cwd,
|
|
1496
|
+
home,
|
|
1497
|
+
inspectCall,
|
|
1498
|
+
loadImage,
|
|
1499
|
+
t,
|
|
1500
|
+
children: block.subCalls.length > 0 ? (0, react_jsx_runtime.jsx)("div", {
|
|
1501
|
+
className: ToolCallTree_module_css_default.subCalls,
|
|
1502
|
+
"data-subcalls": true,
|
|
1503
|
+
children: block.subCalls.map((child) => (0, react_jsx_runtime.jsx)(ToolCallBranch, {
|
|
1504
|
+
renderSlot,
|
|
1505
|
+
block: child,
|
|
1506
|
+
cwd,
|
|
1507
|
+
home,
|
|
1508
|
+
openFile,
|
|
1509
|
+
inspectCall,
|
|
1510
|
+
loadImage,
|
|
1511
|
+
t
|
|
1512
|
+
}, child.callId))
|
|
1513
|
+
}) : null
|
|
1514
|
+
});
|
|
1515
|
+
});
|
|
1516
|
+
/**
|
|
1517
|
+
* Render one root Tool call and its recursive children through the same
|
|
1518
|
+
* atomic keyed dispatch.
|
|
1519
|
+
* @param props - whole-Tool owner data and the Tool-owned child-slot share.
|
|
1520
|
+
* @returns the Tool call tree.
|
|
1521
|
+
*/
|
|
1522
|
+
function ToolCallTree({ renderSlot, node, cwd, openFile, inspectCall, loadImage, useHostInfo, t }) {
|
|
1523
|
+
const home = useHostInfo((info) => info.home);
|
|
1524
|
+
const block = node.data.root;
|
|
1525
|
+
return (0, react_jsx_runtime.jsx)(ToolCallBranch, {
|
|
1526
|
+
renderSlot,
|
|
1527
|
+
block,
|
|
1528
|
+
cwd,
|
|
1529
|
+
home,
|
|
1530
|
+
openFile,
|
|
1531
|
+
inspectCall,
|
|
1532
|
+
loadImage,
|
|
1533
|
+
t
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
//#endregion
|
|
1537
|
+
//#region lib/types/client/locale.js
|
|
1538
|
+
/** Locale namespace supplied by the conversation owner to Tool renderers. */
|
|
1539
|
+
const CONVERSATION_NS = "conversation";
|
|
1540
|
+
//#endregion
|
|
1541
|
+
//#region lib/types/client/tool/toolviews/ask-question-row.js
|
|
1542
|
+
function isRecord(value) {
|
|
1543
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1544
|
+
}
|
|
1545
|
+
function parseJson(text) {
|
|
1546
|
+
try {
|
|
1547
|
+
return JSON.parse(text);
|
|
1548
|
+
} catch {
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
/** Answer records from the result JSON; null when the result is malformed. */
|
|
1553
|
+
function answerEntries(text) {
|
|
1554
|
+
const parsed = parseJson(text);
|
|
1555
|
+
if (!isRecord(parsed)) return null;
|
|
1556
|
+
const answers = parsed.answers;
|
|
1557
|
+
if (!Array.isArray(answers) || !answers.every(isRecord)) return null;
|
|
1558
|
+
const entries = [];
|
|
1559
|
+
for (const answer of answers) {
|
|
1560
|
+
if (typeof answer.id !== "string" || !Array.isArray(answer.selected) || !answer.selected.every((item) => typeof item === "string") || answer.custom !== void 0 && typeof answer.custom !== "string") return null;
|
|
1561
|
+
entries.push({
|
|
1562
|
+
id: answer.id,
|
|
1563
|
+
selected: answer.selected,
|
|
1564
|
+
...answer.custom === void 0 ? {} : { custom: answer.custom }
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
return entries;
|
|
1568
|
+
}
|
|
1569
|
+
/** Questions from call JSON; null when pairing with answers would be ambiguous. */
|
|
1570
|
+
function questionEntries(argsRaw) {
|
|
1571
|
+
const parsed = parseJson(argsRaw);
|
|
1572
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.questions) || parsed.questions.length === 0) return null;
|
|
1573
|
+
const questions = [];
|
|
1574
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1575
|
+
for (const question of parsed.questions) {
|
|
1576
|
+
if (!isRecord(question) || typeof question.id !== "string" || typeof question.question !== "string" || ids.has(question.id)) return null;
|
|
1577
|
+
ids.add(question.id);
|
|
1578
|
+
questions.push({
|
|
1579
|
+
id: question.id,
|
|
1580
|
+
question: question.question
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
return questions;
|
|
1584
|
+
}
|
|
1585
|
+
/** Pair questions with result entries by their echoed stable ids. */
|
|
1586
|
+
function pairAnswers(argsRaw, answers) {
|
|
1587
|
+
const questions = questionEntries(argsRaw);
|
|
1588
|
+
if (questions === null || questions.length !== answers.length) return null;
|
|
1589
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1590
|
+
for (const answer of answers) {
|
|
1591
|
+
if (byId.has(answer.id)) return null;
|
|
1592
|
+
byId.set(answer.id, answer);
|
|
1593
|
+
}
|
|
1594
|
+
const paired = [];
|
|
1595
|
+
for (const question of questions) {
|
|
1596
|
+
const answer = byId.get(question.id);
|
|
1597
|
+
if (answer === void 0) return null;
|
|
1598
|
+
paired.push({
|
|
1599
|
+
...question,
|
|
1600
|
+
answers: [...answer.selected, ...answer.custom === void 0 || answer.custom === "" ? [] : [answer.custom]]
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
return paired;
|
|
1604
|
+
}
|
|
1605
|
+
/** Answer summary plus structured transcript content from the two wire JSON documents. */
|
|
1606
|
+
function answeredPresentation(argsRaw, text, t) {
|
|
1607
|
+
const answers = answerEntries(text);
|
|
1608
|
+
if (answers === null) return null;
|
|
1609
|
+
const answered = answers.filter((answer) => answer.selected.length > 0 || (answer.custom ?? "") !== "").length;
|
|
1610
|
+
return {
|
|
1611
|
+
summary: t("ask.answered", {
|
|
1612
|
+
answered,
|
|
1613
|
+
total: answers.length
|
|
1614
|
+
}),
|
|
1615
|
+
questions: pairAnswers(argsRaw, answers)
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
/** Best-effort answered-count summary when strict transcript pairing fails. */
|
|
1619
|
+
function answeredSummary(text, t) {
|
|
1620
|
+
const parsed = parseJson(text);
|
|
1621
|
+
if (!isRecord(parsed)) return null;
|
|
1622
|
+
const answers = parsed.answers;
|
|
1623
|
+
if (!Array.isArray(answers) || !answers.every(isRecord)) return null;
|
|
1624
|
+
const answered = answers.filter((a) => Array.isArray(a.selected) && a.selected.length > 0 || typeof a.custom === "string" && a.custom !== "").length;
|
|
1625
|
+
return t("ask.answered", {
|
|
1626
|
+
answered,
|
|
1627
|
+
total: answers.length
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1630
|
+
/** Summarizes a pending, answered, cancelled, or interrupted question set. */
|
|
1631
|
+
function AskQuestionRow({ toolName, block, inspect, t }) {
|
|
1632
|
+
const model = toolRowModel(toolName, block);
|
|
1633
|
+
const code = "kind" in block ? block.error?.code : void 0;
|
|
1634
|
+
const argsRaw = ("kind" in block ? block.call?.argsRaw : block.argsRaw) ?? "";
|
|
1635
|
+
let summary = model.summary;
|
|
1636
|
+
let state = model.state;
|
|
1637
|
+
let transcript = null;
|
|
1638
|
+
if (code === "ASK_CANCELLED") {
|
|
1639
|
+
summary = t("ask.cancelled");
|
|
1640
|
+
state = "ok";
|
|
1641
|
+
const questions = questionEntries(argsRaw);
|
|
1642
|
+
if (questions !== null) transcript = {
|
|
1643
|
+
kind: "unanswered",
|
|
1644
|
+
questions,
|
|
1645
|
+
verdict: t("ask.cancelledDetail")
|
|
1646
|
+
};
|
|
1647
|
+
} else if (code === "ASK_ABORTED") {
|
|
1648
|
+
summary = t("ask.interrupted");
|
|
1649
|
+
state = "stopped";
|
|
1650
|
+
const questions = questionEntries(argsRaw);
|
|
1651
|
+
if (questions !== null) transcript = {
|
|
1652
|
+
kind: "unanswered",
|
|
1653
|
+
questions,
|
|
1654
|
+
verdict: t("ask.interruptedDetail")
|
|
1655
|
+
};
|
|
1656
|
+
} else if (model.state === "running") summary = t("ask.waiting");
|
|
1657
|
+
else if ("kind" in block && model.state === "ok") {
|
|
1658
|
+
const text = singleResultText(block);
|
|
1659
|
+
if (text !== void 0) {
|
|
1660
|
+
const presentation = answeredPresentation(argsRaw, text, t);
|
|
1661
|
+
summary = presentation?.summary ?? answeredSummary(text, t) ?? model.summary;
|
|
1662
|
+
if (presentation?.questions !== null && presentation?.questions !== void 0) transcript = {
|
|
1663
|
+
kind: "answered",
|
|
1664
|
+
questions: presentation.questions,
|
|
1665
|
+
skippedLabel: t("ask.skipped")
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
return (0, react_jsx_runtime.jsx)(ToolRow, {
|
|
1670
|
+
t,
|
|
1671
|
+
variant: model.variant,
|
|
1672
|
+
toolName,
|
|
1673
|
+
icon: (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconQuestionOutline14, {}),
|
|
1674
|
+
title: t("ask.rowTitle"),
|
|
1675
|
+
summary,
|
|
1676
|
+
bodyRaw: transcript === null ? model.bodyRaw : null,
|
|
1677
|
+
output: transcript === null ? model.output : null,
|
|
1678
|
+
askQuestion: transcript,
|
|
1679
|
+
state,
|
|
1680
|
+
inspect
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
/** Registers the ask-user-question conversation row. */
|
|
1684
|
+
const askQuestionToolview = {
|
|
1685
|
+
name: "ask-question-toolview",
|
|
1686
|
+
inject: ["slots"],
|
|
1687
|
+
apply(ctx) {
|
|
1688
|
+
ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
|
|
1689
|
+
name: "tool.call.toolview",
|
|
1690
|
+
key: "ask_user_question",
|
|
1691
|
+
locale: CONVERSATION_NS
|
|
1692
|
+
}, AskQuestionRow));
|
|
1693
|
+
}
|
|
1694
|
+
};
|
|
1695
|
+
//#endregion
|
|
1696
|
+
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-tool/src/client/tool/toolviews/bash-sample.module.css.mjs
|
|
1697
|
+
const css = ".CY-8Ka_card{flex-direction:column;display:flex}.CY-8Ka_terminal{--dsl-terminal-font:var(--dsw-font-markdown-code-block-small);--dsl-terminal-line-height:18px;--dsl-terminal-output-max-height:224px;border:.5px solid var(--dsw-alias-border-l1);margin:4px 0 4px 4px}.CY-8Ka_ioCard{border:.5px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-markdown-code-block);font:var(--dsw-font-markdown-code-block-small);border-radius:12px;flex-direction:column;margin:4px 0 4px 4px;display:flex}.CY-8Ka_ioSection{grid-template-columns:max-content 1fr;align-items:baseline;column-gap:14px;max-height:150px;padding:12px 16px;display:grid;overflow-y:auto}.CY-8Ka_ioSection::-webkit-scrollbar-thumb{background-clip:padding-box;border:2px solid #0000;border-radius:6px}.CY-8Ka_ioSection::-webkit-scrollbar-track{margin:6px 0}.CY-8Ka_ioLabel{color:var(--dsw-alias-label-caption);align-self:start;position:sticky;top:0}.CY-8Ka_ioDivider{background:var(--dsw-alias-border-l2);flex:none;height:.5px}.CY-8Ka_ioText{white-space:pre-wrap;word-break:break-word;min-width:0;color:var(--dsw-alias-label-secondary)}.CY-8Ka_ioText[data-error]{color:var(--dsw-alias-state-error-primary)}.CY-8Ka_root[data-expandable]{cursor:pointer}.CY-8Ka_root{height:calc(24px + var(--dsh-content-font-delta,0px));align-items:center;min-width:0;display:flex;position:relative;overflow:hidden}.CY-8Ka_root[data-state=running]:after{content:\"\";background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);pointer-events:none;width:300px;animation:2.6s ease-out infinite CY-8Ka_dsh-bash-row-sweep;position:absolute;top:0;bottom:0;left:0}@keyframes CY-8Ka_dsh-bash-row-sweep{0%{left:-300px}90%,to{left:100%}}.CY-8Ka_leading{width:calc(16px + var(--dsh-content-font-delta,0px));height:calc(16px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;margin-right:6px;display:inline-flex;position:relative}.CY-8Ka_leading svg:not([data-state]){width:calc(14px + var(--dsh-content-font-delta,0px));height:calc(14px + var(--dsh-content-font-delta,0px))}.CY-8Ka_chevron{color:var(--dsw-alias-label-secondary)}.CY-8Ka_iconIdle{opacity:1;transition:opacity .1s;display:inline-flex}.CY-8Ka_chevronHover{opacity:0;margin:auto;transition:opacity .1s;position:absolute;inset:0}.CY-8Ka_root:hover .CY-8Ka_iconIdle{opacity:0}.CY-8Ka_root:hover .CY-8Ka_chevronHover{opacity:1}.CY-8Ka_title{font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-secondary);flex:none}.CY-8Ka_sep{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.CY-8Ka_summary{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);flex:auto;overflow:hidden}.CY-8Ka_errorSummary{color:var(--dsw-alias-state-error-primary)}.CY-8Ka_bodyWrap{flex-direction:column;display:flex}.CY-8Ka_inspectButton{border:.5px solid var(--dsw-alias-border-l4);corner-shape:round;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-secondary);cursor:pointer;opacity:0;border-radius:999px;align-self:flex-start;align-items:center;gap:4px;margin:4px 0 2px 4px;padding:2px 8px;font-size:11px;line-height:16px;transition:opacity .1s;display:inline-flex}.CY-8Ka_card:hover .CY-8Ka_inspectButton,.CY-8Ka_inspectButton:focus-visible{opacity:1}.CY-8Ka_inspectButton:hover{background:var(--dsw-alias-interactive-bg-hover-solid);color:var(--dsw-alias-label-primary)}.CY-8Ka_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}";
|
|
1698
|
+
const tagId = "@x1a0f3n9/dsh-client-ui-tool/bash-sample.module.css";
|
|
1699
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
1700
|
+
const tag = document.createElement("style");
|
|
1701
|
+
tag.dataset.plugin = "@x1a0f3n9/dsh-client-ui-tool";
|
|
1702
|
+
tag.dataset.pluginCss = tagId;
|
|
1703
|
+
tag.textContent = css;
|
|
1704
|
+
document.head.appendChild(tag);
|
|
1705
|
+
}
|
|
1706
|
+
var bash_sample_module_css_default = {
|
|
1707
|
+
"bodyWrap": "CY-8Ka_bodyWrap",
|
|
1708
|
+
"card": "CY-8Ka_card",
|
|
1709
|
+
"chevron": "CY-8Ka_chevron",
|
|
1710
|
+
"chevronHover": "CY-8Ka_chevronHover",
|
|
1711
|
+
"dsh-bash-row-sweep": "CY-8Ka_dsh-bash-row-sweep",
|
|
1712
|
+
"errorSummary": "CY-8Ka_errorSummary",
|
|
1713
|
+
"iconIdle": "CY-8Ka_iconIdle",
|
|
1714
|
+
"inspectButton": "CY-8Ka_inspectButton",
|
|
1715
|
+
"ioCard": "CY-8Ka_ioCard",
|
|
1716
|
+
"ioDivider": "CY-8Ka_ioDivider",
|
|
1717
|
+
"ioLabel": "CY-8Ka_ioLabel",
|
|
1718
|
+
"ioSection": "CY-8Ka_ioSection",
|
|
1719
|
+
"ioText": "CY-8Ka_ioText",
|
|
1720
|
+
"leading": "CY-8Ka_leading",
|
|
1721
|
+
"root": "CY-8Ka_root",
|
|
1722
|
+
"sep": "CY-8Ka_sep",
|
|
1723
|
+
"summary": "CY-8Ka_summary",
|
|
1724
|
+
"terminal": "CY-8Ka_terminal",
|
|
1725
|
+
"title": "CY-8Ka_title",
|
|
1726
|
+
"visuallyHidden": "CY-8Ka_visuallyHidden"
|
|
1727
|
+
};
|
|
1728
|
+
//#endregion
|
|
1729
|
+
//#region lib/types/client/tool/toolviews/bash-sample.js
|
|
1730
|
+
function leadingFor(state) {
|
|
1731
|
+
switch (state) {
|
|
1732
|
+
case "error": return (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.StateDot, { state: "error" });
|
|
1733
|
+
case "stopped": return (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.StateDot, { state: "warning" });
|
|
1734
|
+
default: return (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconApiOutline14, { size: 14 });
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
/** Visually hidden status — StateDot is aria-hidden; AT needs a text label. */
|
|
1738
|
+
function stateStatus(state, t) {
|
|
1739
|
+
switch (state) {
|
|
1740
|
+
case "running": return t("bash.running");
|
|
1741
|
+
case "error": return t("bash.failed");
|
|
1742
|
+
case "stopped": return t("bash.stopped");
|
|
1743
|
+
default: return null;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
/** Renders expandable Bash output with an accessible lifecycle label. */
|
|
1747
|
+
function BashRow({ toolName, block, sessionId, useSessions, inspect, t }) {
|
|
1748
|
+
const model = toolRowModel(toolName, block);
|
|
1749
|
+
const terminalModel = terminalCardModel(block, useSessions((list) => list.byId[sessionId]?.cwd));
|
|
1750
|
+
const terminal = terminalModel === null ? null : localizeTerminalCardModel(terminalModel, t);
|
|
1751
|
+
const state = model.state === "ok" && terminalModel !== null && terminalFailed(terminalModel) ? "error" : model.state;
|
|
1752
|
+
const status = stateStatus(state, t);
|
|
1753
|
+
const [expanded, setExpanded] = (0, react.useState)(false);
|
|
1754
|
+
const genericBody = terminal === null && (model.state === "error" || isSettledPersistentShellCall(block) || isSpilledShellCall(block)) && (model.bodyRaw !== null || model.output !== null);
|
|
1755
|
+
const expandable = terminal !== null || genericBody;
|
|
1756
|
+
const open = expanded && expandable;
|
|
1757
|
+
const body = (0, react.useMemo)(() => open && genericBody && model.bodyRaw !== null ? formatToolBody(model.variant, model.bodyRaw) : null, [
|
|
1758
|
+
genericBody,
|
|
1759
|
+
model.bodyRaw,
|
|
1760
|
+
model.variant,
|
|
1761
|
+
open
|
|
1762
|
+
]);
|
|
1763
|
+
const failureLine = model.state === "error" ? model.errorSummary : null;
|
|
1764
|
+
const toggleExpand = () => {
|
|
1765
|
+
setExpanded((v) => !v);
|
|
1766
|
+
};
|
|
1767
|
+
const toggleFromKeyboard = (event) => {
|
|
1768
|
+
if (!expandable || event.key !== "Enter" && event.key !== " ") return;
|
|
1769
|
+
event.preventDefault();
|
|
1770
|
+
toggleExpand();
|
|
1771
|
+
};
|
|
1772
|
+
const leading = open ? (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconChevronDownOutline14, { className: bash_sample_module_css_default.chevron }) : expandable ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("span", {
|
|
1773
|
+
className: bash_sample_module_css_default.iconIdle,
|
|
1774
|
+
children: leadingFor(state)
|
|
1775
|
+
}), (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconChevronDownOutline14, { className: clsx(bash_sample_module_css_default.chevron, bash_sample_module_css_default.chevronHover) })] }) : leadingFor(state);
|
|
1776
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
1777
|
+
className: bash_sample_module_css_default.card,
|
|
1778
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
1779
|
+
className: bash_sample_module_css_default.root,
|
|
1780
|
+
"data-sample": "bash",
|
|
1781
|
+
"data-variant": "bash",
|
|
1782
|
+
"data-state": state,
|
|
1783
|
+
"data-expandable": expandable || void 0,
|
|
1784
|
+
role: expandable ? "button" : void 0,
|
|
1785
|
+
tabIndex: expandable ? 0 : void 0,
|
|
1786
|
+
"aria-expanded": expandable ? open : void 0,
|
|
1787
|
+
onClick: expandable ? toggleExpand : void 0,
|
|
1788
|
+
onKeyDown: expandable ? toggleFromKeyboard : void 0,
|
|
1789
|
+
children: [
|
|
1790
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1791
|
+
className: bash_sample_module_css_default.leading,
|
|
1792
|
+
children: leading
|
|
1793
|
+
}),
|
|
1794
|
+
status !== null && (0, react_jsx_runtime.jsx)("span", {
|
|
1795
|
+
className: bash_sample_module_css_default.visuallyHidden,
|
|
1796
|
+
children: status
|
|
1797
|
+
}),
|
|
1798
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1799
|
+
className: bash_sample_module_css_default.title,
|
|
1800
|
+
children: t(model.titleKey)
|
|
1801
|
+
}),
|
|
1802
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1803
|
+
className: bash_sample_module_css_default.sep,
|
|
1804
|
+
"aria-hidden": true
|
|
1805
|
+
}),
|
|
1806
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1807
|
+
className: clsx(bash_sample_module_css_default.summary, failureLine !== null && bash_sample_module_css_default.errorSummary),
|
|
1808
|
+
children: failureLine ?? terminal?.description ?? model.summary
|
|
1809
|
+
})
|
|
1810
|
+
]
|
|
1811
|
+
}), open && (0, react_jsx_runtime.jsxs)("div", {
|
|
1812
|
+
className: bash_sample_module_css_default.bodyWrap,
|
|
1813
|
+
children: [terminal !== null ? (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.TerminalBlock, {
|
|
1814
|
+
...terminal.card,
|
|
1815
|
+
maxLines: Infinity,
|
|
1816
|
+
labels: terminalBlockLabels(t),
|
|
1817
|
+
className: bash_sample_module_css_default.terminal
|
|
1818
|
+
}) : (0, react_jsx_runtime.jsxs)("div", {
|
|
1819
|
+
className: bash_sample_module_css_default.ioCard,
|
|
1820
|
+
children: [
|
|
1821
|
+
body !== null && (0, react_jsx_runtime.jsxs)("div", {
|
|
1822
|
+
className: bash_sample_module_css_default.ioSection,
|
|
1823
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
1824
|
+
className: bash_sample_module_css_default.ioLabel,
|
|
1825
|
+
children: t("row.input")
|
|
1826
|
+
}), (0, react_jsx_runtime.jsx)("span", {
|
|
1827
|
+
className: bash_sample_module_css_default.ioText,
|
|
1828
|
+
children: body
|
|
1829
|
+
})]
|
|
1830
|
+
}),
|
|
1831
|
+
body !== null && model.output !== null && (0, react_jsx_runtime.jsx)("span", {
|
|
1832
|
+
className: bash_sample_module_css_default.ioDivider,
|
|
1833
|
+
"aria-hidden": true
|
|
1834
|
+
}),
|
|
1835
|
+
model.output !== null && (0, react_jsx_runtime.jsxs)("div", {
|
|
1836
|
+
className: bash_sample_module_css_default.ioSection,
|
|
1837
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
1838
|
+
className: bash_sample_module_css_default.ioLabel,
|
|
1839
|
+
children: t("row.output")
|
|
1840
|
+
}), (0, react_jsx_runtime.jsx)("span", {
|
|
1841
|
+
className: bash_sample_module_css_default.ioText,
|
|
1842
|
+
"data-error": state === "error" || void 0,
|
|
1843
|
+
children: model.output
|
|
1844
|
+
})]
|
|
1845
|
+
})
|
|
1846
|
+
]
|
|
1847
|
+
}), inspect !== void 0 && (0, react_jsx_runtime.jsxs)("button", {
|
|
1848
|
+
type: "button",
|
|
1849
|
+
className: bash_sample_module_css_default.inspectButton,
|
|
1850
|
+
onClick: inspect,
|
|
1851
|
+
children: [(0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconInspectOutline12, {}), t("row.inspect")]
|
|
1852
|
+
})]
|
|
1853
|
+
})]
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1856
|
+
/** Registers the standalone Bash conversation-row sample. */
|
|
1857
|
+
const bashToolviewSample = {
|
|
1858
|
+
name: "bash-toolview-sample",
|
|
1859
|
+
inject: ["slots"],
|
|
1860
|
+
apply(ctx) {
|
|
1861
|
+
ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
|
|
1862
|
+
name: "tool.call.toolview",
|
|
1863
|
+
key: "bash",
|
|
1864
|
+
locale: CONVERSATION_NS
|
|
1865
|
+
}, BashRow));
|
|
1866
|
+
}
|
|
1867
|
+
};
|
|
1868
|
+
//#endregion
|
|
1869
|
+
//#region lib/types/client/tool/toolviews/file-mutation-row.js
|
|
1870
|
+
/**
|
|
1871
|
+
* Lets users expand an applied file diff and open the reported path.
|
|
1872
|
+
*/
|
|
1873
|
+
function FileMutationRow({ toolName, block, cwd, home, openFile, inspect, t }) {
|
|
1874
|
+
const model = toolRowModel(toolName, block, cwd, home);
|
|
1875
|
+
const diff = diffCardModel(block);
|
|
1876
|
+
return (0, react_jsx_runtime.jsx)(ToolRow, {
|
|
1877
|
+
t,
|
|
1878
|
+
variant: model.variant,
|
|
1879
|
+
toolName,
|
|
1880
|
+
icon: (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconEditOutline16, { size: 14 }),
|
|
1881
|
+
title: t(model.titleKey),
|
|
1882
|
+
summary: model.summary,
|
|
1883
|
+
output: model.output,
|
|
1884
|
+
errorSummary: model.errorSummary,
|
|
1885
|
+
diff,
|
|
1886
|
+
state: model.state,
|
|
1887
|
+
filePath: model.filePath,
|
|
1888
|
+
onOpenFile: openFile,
|
|
1889
|
+
inspect
|
|
1890
|
+
});
|
|
1891
|
+
}
|
|
1892
|
+
/** Registers the edit and write conversation rows. */
|
|
1893
|
+
const fileMutationToolview = {
|
|
1894
|
+
name: "file-mutation-toolview",
|
|
1895
|
+
inject: ["slots"],
|
|
1896
|
+
apply(ctx) {
|
|
1897
|
+
ctx.slots.inject("tool.call.toolview", function* () {
|
|
1898
|
+
yield ctx.slots.register({
|
|
1899
|
+
name: "tool.call.toolview",
|
|
1900
|
+
key: "edit",
|
|
1901
|
+
locale: CONVERSATION_NS
|
|
1902
|
+
}, FileMutationRow);
|
|
1903
|
+
yield ctx.slots.register({
|
|
1904
|
+
name: "tool.call.toolview",
|
|
1905
|
+
key: "write",
|
|
1906
|
+
locale: CONVERSATION_NS
|
|
1907
|
+
}, FileMutationRow);
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1911
|
+
//#endregion
|
|
1912
|
+
//#region lib/types/client/tool/toolviews/read-family-row.js
|
|
1913
|
+
/**
|
|
1914
|
+
* Compose a read-family row: the shared chrome and model-derived fields, plus the
|
|
1915
|
+
* caller's card material.
|
|
1916
|
+
* @param props - the toolview runtime share and locale seat.
|
|
1917
|
+
* @param card - the card props this row owns.
|
|
1918
|
+
* @returns the assembled ToolRow.
|
|
1919
|
+
*/
|
|
1920
|
+
function readFamilyRow({ toolName, block, cwd, home, openFile, inspect, t }, card) {
|
|
1921
|
+
const model = toolRowModel(toolName, block, cwd, home);
|
|
1922
|
+
return (0, react_jsx_runtime.jsx)(ToolRow, {
|
|
1923
|
+
t,
|
|
1924
|
+
variant: model.variant,
|
|
1925
|
+
toolName,
|
|
1926
|
+
icon: (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconBrowseOutline16, { size: 14 }),
|
|
1927
|
+
title: t(model.titleKey),
|
|
1928
|
+
summary: model.summary,
|
|
1929
|
+
bodyRaw: null,
|
|
1930
|
+
output: model.output,
|
|
1931
|
+
errorSummary: model.errorSummary,
|
|
1932
|
+
...card,
|
|
1933
|
+
state: model.state,
|
|
1934
|
+
filePath: model.filePath,
|
|
1935
|
+
onOpenFile: openFile,
|
|
1936
|
+
inspect
|
|
1937
|
+
});
|
|
1938
|
+
}
|
|
1939
|
+
//#endregion
|
|
1940
|
+
//#region lib/types/client/tool/toolviews/read-row.js
|
|
1941
|
+
/**
|
|
1942
|
+
* Lets users expand a completed read result and open its reported path at the
|
|
1943
|
+
* line the call started from.
|
|
1944
|
+
*/
|
|
1945
|
+
function ReadRow(props) {
|
|
1946
|
+
const { block, cwd, home } = props;
|
|
1947
|
+
return readFamilyRow(props, {
|
|
1948
|
+
read: readCardModel(block, cwd, home),
|
|
1949
|
+
filePathLine: readCallLine(block)
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
/** Registers the read tool's conversation row. */
|
|
1953
|
+
const readToolview = {
|
|
1954
|
+
name: "read-toolview",
|
|
1955
|
+
inject: ["slots"],
|
|
1956
|
+
apply(ctx) {
|
|
1957
|
+
ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
|
|
1958
|
+
name: "tool.call.toolview",
|
|
1959
|
+
key: "read",
|
|
1960
|
+
locale: CONVERSATION_NS
|
|
1961
|
+
}, ReadRow));
|
|
1962
|
+
}
|
|
1963
|
+
};
|
|
1964
|
+
//#endregion
|
|
1965
|
+
//#region lib/types/client/tool/models/image-card-model.js
|
|
1966
|
+
/**
|
|
1967
|
+
* Whether a wire value is a usable pixel or byte measure.
|
|
1968
|
+
* @param value - unvalidated wire value.
|
|
1969
|
+
* @returns true when it is a positive integer.
|
|
1970
|
+
*/
|
|
1971
|
+
function positiveInteger(value) {
|
|
1972
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
1973
|
+
}
|
|
1974
|
+
/** The envelope `formatImageReadOutput` writes, matched by shape. */
|
|
1975
|
+
const IMAGE_ENVELOPE = /^<path>[^\n]*<\/path>\n<type>image<\/type>\n<content>\n[\s\S]*\n<\/content>$/u;
|
|
1976
|
+
/** The media types a durable image block may claim; anything else declines.
|
|
1977
|
+
* Hand-written mirror of `ImageMediaType` from dsh-attachment — the wire
|
|
1978
|
+
* boundary needs a runtime check and feature plugins must not import values
|
|
1979
|
+
* from each other; a new member added there must be added here too, or the
|
|
1980
|
+
* decline point below silently degrades that image to the generic card. */
|
|
1981
|
+
const IMAGE_MEDIA_TYPES = new Set([
|
|
1982
|
+
"image/png",
|
|
1983
|
+
"image/jpeg",
|
|
1984
|
+
"image/webp",
|
|
1985
|
+
"image/gif"
|
|
1986
|
+
]);
|
|
1987
|
+
/** Runtime membership check; the cast is safe because the set holds exactly the four members. */
|
|
1988
|
+
function isImageMediaType(value) {
|
|
1989
|
+
return IMAGE_MEDIA_TYPES.has(value);
|
|
1990
|
+
}
|
|
1991
|
+
/**
|
|
1992
|
+
* Narrow the persisted metadata, defensively. Every field arrives unvalidated on
|
|
1993
|
+
* replay (an obsolete or hand-edited log reaches here), so any mismatch declines
|
|
1994
|
+
* to the generic card rather than throwing. An absent `meta` (a nested call
|
|
1995
|
+
* persists none) leaves the path to the call's own `file_path` argument.
|
|
1996
|
+
*
|
|
1997
|
+
* The attachment id is checked for existence only: it is opaque and
|
|
1998
|
+
* provider-owned, and consumers must not parse that representation, so
|
|
1999
|
+
* pattern-matching the local content-address form would reject a legitimate id
|
|
2000
|
+
* minted by an alternative store.
|
|
2001
|
+
* @param meta - persisted presentation metadata of unknown shape.
|
|
2002
|
+
* @returns the narrowed path, or null when it does not match.
|
|
2003
|
+
*/
|
|
2004
|
+
function imageMeta(meta) {
|
|
2005
|
+
if (typeof meta !== "object" || meta === null || Array.isArray(meta)) return null;
|
|
2006
|
+
const { path } = meta;
|
|
2007
|
+
if (typeof path !== "string" || path === "") return null;
|
|
2008
|
+
return { path };
|
|
2009
|
+
}
|
|
2010
|
+
/**
|
|
2011
|
+
* Narrow every attachment reference carried by the result's image blocks, in
|
|
2012
|
+
* order.
|
|
2013
|
+
*
|
|
2014
|
+
* The content is the single source of truth for the references: it is what the
|
|
2015
|
+
* tool actually returned and what a post-execute hook would replace together
|
|
2016
|
+
* with the rest of the content. Every field arrives unvalidated over the wire,
|
|
2017
|
+
* so any malformed image block declines to the generic card rather than
|
|
2018
|
+
* rendering a partial gallery.
|
|
2019
|
+
*
|
|
2020
|
+
* The attachment id is checked for existence only — it is opaque and
|
|
2021
|
+
* provider-owned, so pattern-matching the local content-address form would reject
|
|
2022
|
+
* a legitimate id minted by an alternative store.
|
|
2023
|
+
* @param content - the settled result's content blocks.
|
|
2024
|
+
* @returns the narrowed references, or null when no valid image block is present.
|
|
2025
|
+
*/
|
|
2026
|
+
function imageReferences(content) {
|
|
2027
|
+
const refs = [];
|
|
2028
|
+
for (const part of content) {
|
|
2029
|
+
if (typeof part !== "object" || part === null) continue;
|
|
2030
|
+
const { type, attachment } = part;
|
|
2031
|
+
if (type !== "image") continue;
|
|
2032
|
+
if (typeof attachment !== "object" || attachment === null || Array.isArray(attachment)) return null;
|
|
2033
|
+
const { attachmentId, mediaType, bytes, width, height, name, originalDimensions } = attachment;
|
|
2034
|
+
if (typeof attachmentId !== "string" || attachmentId === "") return null;
|
|
2035
|
+
if (typeof mediaType !== "string" || !isImageMediaType(mediaType)) return null;
|
|
2036
|
+
if (!positiveInteger(bytes) || !positiveInteger(width) || !positiveInteger(height)) return null;
|
|
2037
|
+
if (name !== void 0 && typeof name !== "string") return null;
|
|
2038
|
+
let inputDimensions;
|
|
2039
|
+
if (originalDimensions !== void 0) {
|
|
2040
|
+
if (typeof originalDimensions !== "object" || originalDimensions === null || Array.isArray(originalDimensions)) return null;
|
|
2041
|
+
const { width: inputWidth, height: inputHeight } = originalDimensions;
|
|
2042
|
+
if (!positiveInteger(inputWidth) || !positiveInteger(inputHeight)) return null;
|
|
2043
|
+
inputDimensions = {
|
|
2044
|
+
width: inputWidth,
|
|
2045
|
+
height: inputHeight
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
refs.push({
|
|
2049
|
+
attachmentId,
|
|
2050
|
+
mediaType,
|
|
2051
|
+
bytes,
|
|
2052
|
+
width,
|
|
2053
|
+
height,
|
|
2054
|
+
...name === void 0 ? {} : { name },
|
|
2055
|
+
...inputDimensions === void 0 ? {} : { originalDimensions: inputDimensions }
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
return refs.length > 0 ? refs : null;
|
|
2059
|
+
}
|
|
2060
|
+
/**
|
|
2061
|
+
* Read the text of every text block of a settled image result, joined in order.
|
|
2062
|
+
*
|
|
2063
|
+
* The envelope is one of them; a post-execute hook that appends further text
|
|
2064
|
+
* blocks keeps them visible under the gallery instead of being dropped. The
|
|
2065
|
+
* envelope shape is still the recognition gate: a result without it is not a
|
|
2066
|
+
* well-formed image read and declines.
|
|
2067
|
+
* @param content - the settled result's content blocks.
|
|
2068
|
+
* @returns the joined text, or null when no envelope-shaped block is present.
|
|
2069
|
+
*/
|
|
2070
|
+
function imageTexts(content) {
|
|
2071
|
+
const parts = [];
|
|
2072
|
+
let sawEnvelope = false;
|
|
2073
|
+
for (const part of content) {
|
|
2074
|
+
if (part.type !== "text" || typeof part.text !== "string") continue;
|
|
2075
|
+
if (IMAGE_ENVELOPE.test(part.text)) sawEnvelope = true;
|
|
2076
|
+
parts.push(part.text);
|
|
2077
|
+
}
|
|
2078
|
+
return sawEnvelope && parts.length > 0 ? parts.join("\n") : null;
|
|
2079
|
+
}
|
|
2080
|
+
/**
|
|
2081
|
+
* Whether the content carries only blocks the card consumes.
|
|
2082
|
+
*
|
|
2083
|
+
* `ContentBlock` is a merge-extensible union, so a post-execute hook could
|
|
2084
|
+
* append a block of a type this card does not render (reasoning, an extension
|
|
2085
|
+
* type, or a non-object). Rendering the card anyway would silently hide that
|
|
2086
|
+
* block, so anything beyond a well-formed text or image object declines to the
|
|
2087
|
+
* generic card, which shows the flattened content.
|
|
2088
|
+
* @param content - the settled result's content blocks.
|
|
2089
|
+
* @returns true when every block is a text or image object with usable fields.
|
|
2090
|
+
*/
|
|
2091
|
+
function fullyRendered(content) {
|
|
2092
|
+
return content.every((part) => {
|
|
2093
|
+
if (typeof part !== "object" || part === null) return false;
|
|
2094
|
+
const { type, text } = part;
|
|
2095
|
+
return type === "image" || type === "text" && typeof text === "string";
|
|
2096
|
+
});
|
|
2097
|
+
}
|
|
2098
|
+
/**
|
|
2099
|
+
* Derive a settled image card after validating the call head, persisted
|
|
2100
|
+
* metadata (or its argument fallback), and the model-facing image envelope.
|
|
2101
|
+
*
|
|
2102
|
+
* The card is result-side only: a call carries no content until `execute`
|
|
2103
|
+
* returns, so a running `read_image` has none and this returns null for it.
|
|
2104
|
+
* Both root and nested calls settle as ToolResultNode; the nested one (a
|
|
2105
|
+
* read_image dispatched from inside run_code) persists no presentationMeta, so
|
|
2106
|
+
* its label falls back to the call's own `file_path` argument.
|
|
2107
|
+
* @param block - running or settled Tool block.
|
|
2108
|
+
* @param sessionCwd - the session workspace root; a workspace-rooted absolute
|
|
2109
|
+
* path label displays relative to it. Absent leaves the path as authored.
|
|
2110
|
+
* @param home - host account home; a leftover POSIX home path displays as `~`.
|
|
2111
|
+
* @returns the image-card props, or null for the generic path.
|
|
2112
|
+
*/
|
|
2113
|
+
function imageCardModel(block, sessionCwd, home) {
|
|
2114
|
+
if (!("kind" in block) || block.isError) return null;
|
|
2115
|
+
const call = parsedToolCall(block);
|
|
2116
|
+
if (call?.name !== "read_image") return null;
|
|
2117
|
+
const { file_path: filePath } = call.args;
|
|
2118
|
+
if (typeof filePath !== "string" || filePath.trim() === "") return null;
|
|
2119
|
+
const path = imageMeta(block.meta)?.path ?? (block.parentCallId !== void 0 ? filePath : null);
|
|
2120
|
+
if (path === null) return null;
|
|
2121
|
+
if (!fullyRendered(block.content)) return null;
|
|
2122
|
+
const refs = imageReferences(block.content);
|
|
2123
|
+
if (refs === null) return null;
|
|
2124
|
+
const text = imageTexts(block.content);
|
|
2125
|
+
if (text === null) return null;
|
|
2126
|
+
return {
|
|
2127
|
+
label: abbreviateHomePath(relativizeToCwd(path, sessionCwd), home),
|
|
2128
|
+
images: refs.map((ref) => ({ attachment: ref })),
|
|
2129
|
+
text
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
//#endregion
|
|
2133
|
+
//#region lib/types/client/tool/toolviews/read-image-row.js
|
|
2134
|
+
/**
|
|
2135
|
+
* read_image row: the read-family chrome with the durably committed image as the
|
|
2136
|
+
* row's collapsed-by-default card body, rendered through the `tool.call.images`
|
|
2137
|
+
* slot this entry declares.
|
|
2138
|
+
*/
|
|
2139
|
+
function ReadImageRow(props) {
|
|
2140
|
+
const { block, cwd, home, renderSlot, loadImage } = props;
|
|
2141
|
+
return readFamilyRow(props, {
|
|
2142
|
+
image: imageCardModel(block, cwd, home),
|
|
2143
|
+
renderSlot,
|
|
2144
|
+
loadImage
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
/**
|
|
2148
|
+
* The read_image row as a plain registrant plugin following the atomic Tool-view
|
|
2149
|
+
* declaration across independent activation and reload lifetimes. Declaring
|
|
2150
|
+
* `tool.call.images` as a child slot authorizes this entry's `renderSlot` to
|
|
2151
|
+
* dispatch the gallery.
|
|
2152
|
+
*/
|
|
2153
|
+
const readImageToolview = {
|
|
2154
|
+
name: "read-image-toolview",
|
|
2155
|
+
inject: ["slots"],
|
|
2156
|
+
/**
|
|
2157
|
+
* Register the read_image row into the Tool-owned keyed view slot.
|
|
2158
|
+
* @param ctx - registrant context (disposal rides ctx.effect inside slots.register).
|
|
2159
|
+
*/
|
|
2160
|
+
apply(ctx) {
|
|
2161
|
+
ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
|
|
2162
|
+
name: "tool.call.toolview",
|
|
2163
|
+
key: "read_image",
|
|
2164
|
+
locale: CONVERSATION_NS,
|
|
2165
|
+
children: { "tool.call.images": {
|
|
2166
|
+
kind: "single",
|
|
2167
|
+
scope: "session"
|
|
2168
|
+
} }
|
|
2169
|
+
}, ReadImageRow));
|
|
2170
|
+
}
|
|
2171
|
+
};
|
|
2172
|
+
//#endregion
|
|
2173
|
+
//#region lib/types/client/tool/toolviews/search-row.js
|
|
2174
|
+
const SEARCH_TITLE_KEYS = {
|
|
2175
|
+
grep: "tool.title.grep",
|
|
2176
|
+
glob: "tool.title.glob"
|
|
2177
|
+
};
|
|
2178
|
+
/** Lets users expand grep or glob results and recover capped searches. */
|
|
2179
|
+
function SearchRow({ toolName, block, inspect, t }) {
|
|
2180
|
+
const model = toolRowModel(toolName, block);
|
|
2181
|
+
const search = searchCardModel(block);
|
|
2182
|
+
return (0, react_jsx_runtime.jsx)(ToolRow, {
|
|
2183
|
+
t,
|
|
2184
|
+
variant: model.variant,
|
|
2185
|
+
toolName,
|
|
2186
|
+
icon: (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconSearchOutline16, { size: 14 }),
|
|
2187
|
+
title: t(toolName === "grep" ? SEARCH_TITLE_KEYS.grep : toolName === "glob" ? SEARCH_TITLE_KEYS.glob : model.titleKey),
|
|
2188
|
+
summary: model.summary,
|
|
2189
|
+
output: model.output,
|
|
2190
|
+
errorSummary: model.errorSummary,
|
|
2191
|
+
search,
|
|
2192
|
+
state: model.state,
|
|
2193
|
+
inspect
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
2196
|
+
/** Registers the grep and glob conversation rows. */
|
|
2197
|
+
const searchToolview = {
|
|
2198
|
+
name: "search-toolview",
|
|
2199
|
+
inject: ["slots"],
|
|
2200
|
+
apply(ctx) {
|
|
2201
|
+
ctx.slots.inject("tool.call.toolview", function* () {
|
|
2202
|
+
yield ctx.slots.register({
|
|
2203
|
+
name: "tool.call.toolview",
|
|
2204
|
+
key: "grep",
|
|
2205
|
+
locale: CONVERSATION_NS
|
|
2206
|
+
}, SearchRow);
|
|
2207
|
+
yield ctx.slots.register({
|
|
2208
|
+
name: "tool.call.toolview",
|
|
2209
|
+
key: "glob",
|
|
2210
|
+
locale: CONVERSATION_NS
|
|
2211
|
+
}, SearchRow);
|
|
2212
|
+
});
|
|
2213
|
+
}
|
|
2214
|
+
};
|
|
2215
|
+
//#endregion
|
|
2216
|
+
//#region lib/types/client/tool/toolviews/plan-summary.js
|
|
2217
|
+
/**
|
|
2218
|
+
* Pure plan derivation for the todo_write row's one-line summary. Several items
|
|
2219
|
+
* may be `in_progress` at once — parallel work runs concurrent tasks, so a
|
|
2220
|
+
* summary built from one active item would silently drop the rest. The plan
|
|
2221
|
+
* strip header derives its own counts inline and shares nothing with this, so
|
|
2222
|
+
* this stays inside the toolviews domain rather than in `contract/` (the
|
|
2223
|
+
* inter-domain face).
|
|
2224
|
+
* @module
|
|
2225
|
+
*/
|
|
2226
|
+
/**
|
|
2227
|
+
* Derive the counts and the active summary from a whole-list snapshot. It names
|
|
2228
|
+
* the first `in_progress` item and counts the remaining active ones, so a
|
|
2229
|
+
* parallel plan reports how many tasks are running rather than naming one and
|
|
2230
|
+
* hiding the others. `activeContent` is null when nothing is in progress, or
|
|
2231
|
+
* when the first active item's content is missing, mistyped, or blank once
|
|
2232
|
+
* trimmed — the tool's own rule for usable content, applied here because a
|
|
2233
|
+
* rejected call keeps its args verbatim. The row then renders the counts alone
|
|
2234
|
+
* rather than falling back to the generic tool summary: the counts are already
|
|
2235
|
+
* known to be good, and the active-item clause is the only part an unusable
|
|
2236
|
+
* name costs.
|
|
2237
|
+
* @param todos - the whole list, in model order.
|
|
2238
|
+
* @returns the done/total counts and the two summary halves.
|
|
2239
|
+
*/
|
|
2240
|
+
function planSummary(todos) {
|
|
2241
|
+
const active = todos.filter((t) => t.status === "in_progress");
|
|
2242
|
+
const first = active[0]?.content;
|
|
2243
|
+
const named = typeof first === "string" && first.trim() !== "";
|
|
2244
|
+
return {
|
|
2245
|
+
done: todos.filter((t) => t.status === "completed").length,
|
|
2246
|
+
total: todos.length,
|
|
2247
|
+
activeContent: named ? first : null,
|
|
2248
|
+
activeExtra: named ? active.length - 1 : 0
|
|
2249
|
+
};
|
|
2250
|
+
}
|
|
2251
|
+
//#endregion
|
|
2252
|
+
//#region lib/types/client/tool/toolviews/todo-row.js
|
|
2253
|
+
function isItem(value) {
|
|
2254
|
+
return typeof value === "object" && value !== null;
|
|
2255
|
+
}
|
|
2256
|
+
function summarize(argsRaw, t) {
|
|
2257
|
+
let parsed;
|
|
2258
|
+
try {
|
|
2259
|
+
parsed = JSON.parse(argsRaw);
|
|
2260
|
+
} catch {
|
|
2261
|
+
return null;
|
|
2262
|
+
}
|
|
2263
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
2264
|
+
const todos = parsed.todos;
|
|
2265
|
+
if (!Array.isArray(todos) || !todos.every(isItem)) return null;
|
|
2266
|
+
const { done, total, activeContent, activeExtra } = planSummary(todos);
|
|
2267
|
+
const head = t("todo.completed", {
|
|
2268
|
+
done,
|
|
2269
|
+
total
|
|
2270
|
+
});
|
|
2271
|
+
return {
|
|
2272
|
+
text: activeContent === null ? head : `${head} · ${activeContent}`,
|
|
2273
|
+
extra: activeExtra
|
|
2274
|
+
};
|
|
2275
|
+
}
|
|
2276
|
+
/** Summarizes a plan update without presenting a cancelled call as completed. */
|
|
2277
|
+
function TodoRow({ toolName, block, inspect, t }) {
|
|
2278
|
+
const model = toolRowModel(toolName, block);
|
|
2279
|
+
const summary = summarize(("kind" in block ? block.call?.argsRaw : block.argsRaw) ?? "", t) ?? {
|
|
2280
|
+
text: model.summary,
|
|
2281
|
+
extra: 0
|
|
2282
|
+
};
|
|
2283
|
+
return (0, react_jsx_runtime.jsx)(ToolRow, {
|
|
2284
|
+
t,
|
|
2285
|
+
variant: model.variant,
|
|
2286
|
+
toolName,
|
|
2287
|
+
icon: (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconChecklistOutline14, {}),
|
|
2288
|
+
title: t("todo.rowTitle"),
|
|
2289
|
+
summary: summary.text,
|
|
2290
|
+
summarySuffix: summary.extra > 0 ? `+${summary.extra}` : null,
|
|
2291
|
+
bodyRaw: model.bodyRaw,
|
|
2292
|
+
output: model.output,
|
|
2293
|
+
errorSummary: model.errorSummary,
|
|
2294
|
+
state: model.state,
|
|
2295
|
+
inspect
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2298
|
+
/** Registers the todo conversation row. */
|
|
2299
|
+
const todoToolview = {
|
|
2300
|
+
name: "todo-toolview",
|
|
2301
|
+
inject: ["slots"],
|
|
2302
|
+
apply(ctx) {
|
|
2303
|
+
ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
|
|
2304
|
+
name: "tool.call.toolview",
|
|
2305
|
+
key: "todo_write",
|
|
2306
|
+
locale: CONVERSATION_NS
|
|
2307
|
+
}, TodoRow));
|
|
2308
|
+
}
|
|
2309
|
+
};
|
|
2310
|
+
//#endregion
|
|
2311
|
+
//#region lib/types/client/tool/toolviews/web-row.js
|
|
2312
|
+
const WEB_TITLE_KEYS = {
|
|
2313
|
+
web_search: "tool.title.webSearch",
|
|
2314
|
+
web_fetch: "tool.title.webFetch"
|
|
2315
|
+
};
|
|
2316
|
+
/** Lets users expand a completed web search or fetch result. */
|
|
2317
|
+
function WebRow({ toolName, block, inspect, t }) {
|
|
2318
|
+
const model = toolRowModel(toolName, block);
|
|
2319
|
+
const web = webCardModel(block);
|
|
2320
|
+
const icon = toolName === "web_fetch" ? (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconBrowseOutline16, { size: 14 }) : (0, react_jsx_runtime.jsx)(_x1a0f3n9_dsh_client_ui_primitives.IconGlobeOutline14, { size: 14 });
|
|
2321
|
+
return (0, react_jsx_runtime.jsx)(ToolRow, {
|
|
2322
|
+
t,
|
|
2323
|
+
variant: model.variant,
|
|
2324
|
+
toolName,
|
|
2325
|
+
icon,
|
|
2326
|
+
title: t(toolName === "web_search" ? WEB_TITLE_KEYS.web_search : toolName === "web_fetch" ? WEB_TITLE_KEYS.web_fetch : model.titleKey),
|
|
2327
|
+
summary: model.summary,
|
|
2328
|
+
output: model.output,
|
|
2329
|
+
errorSummary: model.errorSummary,
|
|
2330
|
+
web,
|
|
2331
|
+
state: model.state,
|
|
2332
|
+
inspect
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2335
|
+
/** Registers the web search and fetch conversation rows. */
|
|
2336
|
+
const webToolview = {
|
|
2337
|
+
name: "web-toolview",
|
|
2338
|
+
inject: ["slots"],
|
|
2339
|
+
apply(ctx) {
|
|
2340
|
+
ctx.slots.inject("tool.call.toolview", function* () {
|
|
2341
|
+
yield ctx.slots.register({
|
|
2342
|
+
name: "tool.call.toolview",
|
|
2343
|
+
key: "web_search",
|
|
2344
|
+
locale: CONVERSATION_NS
|
|
2345
|
+
}, WebRow);
|
|
2346
|
+
yield ctx.slots.register({
|
|
2347
|
+
name: "tool.call.toolview",
|
|
2348
|
+
key: "web_fetch",
|
|
2349
|
+
locale: CONVERSATION_NS
|
|
2350
|
+
}, WebRow);
|
|
2351
|
+
});
|
|
2352
|
+
}
|
|
2353
|
+
};
|
|
2354
|
+
//#endregion
|
|
2355
|
+
//#region lib/types/client/apply.js
|
|
2356
|
+
/** Required services: the slot registry and the Remote face carrying the Host home used for POSIX `~`. */
|
|
2357
|
+
const inject = ["slots", "remote"];
|
|
2358
|
+
/**
|
|
2359
|
+
* Mount the whole-Tool renderers and built-in atomic Tool registrations.
|
|
2360
|
+
* @param ctx - Client root context.
|
|
2361
|
+
*/
|
|
2362
|
+
function apply(ctx) {
|
|
2363
|
+
const hostInfo = {
|
|
2364
|
+
getSnapshot: () => ctx.remote.$host,
|
|
2365
|
+
subscribe: (listener) => ctx.on("connection/reset", listener)
|
|
2366
|
+
};
|
|
2367
|
+
const toolInject = () => ({ hooks: { hostInfo } });
|
|
2368
|
+
ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
|
|
2369
|
+
name: "conversation.chat.node",
|
|
2370
|
+
key: "tool-call",
|
|
2371
|
+
locale: CONVERSATION_NS,
|
|
2372
|
+
children: { "tool.call.toolview": {
|
|
2373
|
+
kind: "keyed",
|
|
2374
|
+
scope: "session"
|
|
2375
|
+
} },
|
|
2376
|
+
inject: toolInject
|
|
2377
|
+
}, ToolCallTree));
|
|
2378
|
+
ctx.plugin(bashToolviewSample);
|
|
2379
|
+
ctx.plugin(readToolview);
|
|
2380
|
+
ctx.plugin(readImageToolview);
|
|
2381
|
+
ctx.plugin(fileMutationToolview);
|
|
2382
|
+
ctx.plugin(searchToolview);
|
|
2383
|
+
ctx.plugin(webToolview);
|
|
2384
|
+
ctx.plugin(todoToolview);
|
|
2385
|
+
ctx.plugin(askQuestionToolview);
|
|
2386
|
+
}
|
|
2387
|
+
//#endregion
|
|
2388
|
+
exports.apply = apply;
|
|
2389
|
+
exports.inject = inject;
|
|
2390
|
+
return module.exports;
|
|
2391
|
+
}
|
|
2392
|
+
});
|
|
2393
|
+
|
|
2394
|
+
//# sourceMappingURL=client.js.map
|