@ian-pascoe/pi-codemode 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +241 -0
- package/package.json +59 -0
- package/src/codemode-cell-transform.ts +612 -0
- package/src/codemode-deno-launch.ts +59 -0
- package/src/codemode-deno-process.ts +208 -0
- package/src/codemode-observer-ui.ts +517 -0
- package/src/codemode-presentation-output.ts +16 -0
- package/src/codemode-runtime.ts +24 -0
- package/src/codemode-session-coordinator.ts +1297 -0
- package/src/codemode-session-files.ts +80 -0
- package/src/codemode-tool-catalog.ts +350 -0
- package/src/codemode-tool-contract.ts +480 -0
- package/src/codemode-tool-exposure.ts +159 -0
- package/src/codemode-tool-rendering.ts +487 -0
- package/src/codemode-worker-protocol.ts +480 -0
- package/src/codemode-worker.ts +1092 -0
- package/src/index.ts +1 -0
- package/src/pi-agent-session-capture.ts +157 -0
- package/src/pi-codemode-extension.ts +469 -0
- package/src/pi-codemode-settings.ts +168 -0
- package/src/pi-tool-bridge.ts +744 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getMarkdownTheme,
|
|
3
|
+
keyText,
|
|
4
|
+
truncateHead,
|
|
5
|
+
type AgentToolResult,
|
|
6
|
+
type Theme,
|
|
7
|
+
type ThemeColor,
|
|
8
|
+
type ToolRenderResultOptions,
|
|
9
|
+
} from "@earendil-works/pi-coding-agent";
|
|
10
|
+
import {
|
|
11
|
+
Container,
|
|
12
|
+
Markdown,
|
|
13
|
+
sliceByColumn,
|
|
14
|
+
Spacer,
|
|
15
|
+
stripTerminalSequences,
|
|
16
|
+
Text,
|
|
17
|
+
visibleWidth,
|
|
18
|
+
type Component,
|
|
19
|
+
} from "@earendil-works/pi-tui";
|
|
20
|
+
import { Type } from "typebox";
|
|
21
|
+
import { Value } from "typebox/value";
|
|
22
|
+
import { formatCodeModePresentationData } from "./codemode-presentation-output.js";
|
|
23
|
+
import {
|
|
24
|
+
CodeModeCancelParametersSchema,
|
|
25
|
+
CodeModeExecuteParametersSchema,
|
|
26
|
+
CodeModeResultDetailsSchema,
|
|
27
|
+
CodeModeResultParametersSchema,
|
|
28
|
+
createCodeModeToolDefinitions,
|
|
29
|
+
type CodeModeCancelParameters,
|
|
30
|
+
type CodeModeErrorCode,
|
|
31
|
+
type CodeModeExecuteParameters,
|
|
32
|
+
isCodeModeJsonObject,
|
|
33
|
+
type CodeModeJsonValue,
|
|
34
|
+
type CodeModePresentationSnapshot,
|
|
35
|
+
type CodeModeResultDetails,
|
|
36
|
+
type CodeModeResultParameters,
|
|
37
|
+
type CodeModeToolOperations,
|
|
38
|
+
} from "./codemode-tool-contract.js";
|
|
39
|
+
|
|
40
|
+
/** Names of the three CodeMode tools with semantic Transcript rendering. */
|
|
41
|
+
export type CodeModeRenderedToolName = "codemode_execute" | "codemode_result" | "codemode_cancel";
|
|
42
|
+
|
|
43
|
+
/** Parsed arguments accepted by one of the three CodeMode Transcript renderers. */
|
|
44
|
+
export type CodeModeRenderedToolParameters =
|
|
45
|
+
| CodeModeExecuteParameters
|
|
46
|
+
| CodeModeResultParameters
|
|
47
|
+
| CodeModeCancelParameters;
|
|
48
|
+
|
|
49
|
+
/** Theme operations used by CodeMode Transcript renderers. */
|
|
50
|
+
export type CodeModeRenderTheme = Pick<Theme, "bold" | "fg">;
|
|
51
|
+
|
|
52
|
+
type CodeModeCellState = CodeModePresentationSnapshot["cell_state"];
|
|
53
|
+
type CodeModeStatusPresentation = {
|
|
54
|
+
readonly color: ThemeColor;
|
|
55
|
+
readonly label: string;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const CODEMODE_STATUS_PRESENTATION = {
|
|
59
|
+
running: { color: "accent", label: "◉ running" },
|
|
60
|
+
completed: { color: "success", label: "✓ completed" },
|
|
61
|
+
failed: { color: "error", label: "× failed" },
|
|
62
|
+
cancelled: { color: "warning", label: "■ cancelled" },
|
|
63
|
+
timed_out: { color: "error", label: "! timed out" },
|
|
64
|
+
} satisfies Record<CodeModeCellState, CodeModeStatusPresentation>;
|
|
65
|
+
const CODEMODE_SCRIPT_MAX_LINES = 200;
|
|
66
|
+
const CODEMODE_PRESENTATION_MAX_BYTES = 50 * 1024;
|
|
67
|
+
const CodeModeJsonStringSchema = Type.String();
|
|
68
|
+
|
|
69
|
+
function sanitizeCodeModeText(text: string): string {
|
|
70
|
+
return (
|
|
71
|
+
stripTerminalSequences(text)
|
|
72
|
+
.replaceAll("\r\n", "\n")
|
|
73
|
+
.replaceAll("\r", "\n")
|
|
74
|
+
// oxlint-disable-next-line eslint/no-control-regex -- SAFETY: Transcript text permits tabs/newlines but must remove every remaining C0/C1 terminal control.
|
|
75
|
+
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g, "")
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function boundedCodeModePreview(text: string, width = 72): string {
|
|
80
|
+
const singleLine = sanitizeCodeModeText(text).replace(/\s+/g, " ").trim();
|
|
81
|
+
if (visibleWidth(singleLine) <= width) return singleLine;
|
|
82
|
+
return `${sliceByColumn(singleLine, 0, width - 1, true).trimEnd()}…`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function firstMeaningfulScriptLine(script: string): string | undefined {
|
|
86
|
+
const line = sanitizeCodeModeText(script)
|
|
87
|
+
.split("\n")
|
|
88
|
+
.map((candidate) => candidate.trim())
|
|
89
|
+
.find(Boolean);
|
|
90
|
+
return line === undefined ? undefined : boundedCodeModePreview(line);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function parseCodeModeRenderedToolParameters(
|
|
94
|
+
toolName: CodeModeRenderedToolName,
|
|
95
|
+
parameters: CodeModeJsonValue,
|
|
96
|
+
): CodeModeRenderedToolParameters | undefined {
|
|
97
|
+
if (toolName === "codemode_execute") {
|
|
98
|
+
return Value.Check(CodeModeExecuteParametersSchema, parameters) ? parameters : undefined;
|
|
99
|
+
}
|
|
100
|
+
if (toolName === "codemode_result") {
|
|
101
|
+
return Value.Check(CodeModeResultParametersSchema, parameters) ? parameters : undefined;
|
|
102
|
+
}
|
|
103
|
+
return Value.Check(CodeModeCancelParametersSchema, parameters) ? parameters : undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function shortCodeModeSessionId(sessionId: string): string {
|
|
107
|
+
const safe = sanitizeCodeModeText(sessionId).replace(/\s+/g, "");
|
|
108
|
+
return sliceByColumn(safe, 0, Math.min(8, visibleWidth(safe)), true);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Resolves one Session ID to the shortest unambiguous CodeMode Transcript label. */
|
|
112
|
+
export type CodeModeSessionPrefixFormatter = (sessionId: string) => string;
|
|
113
|
+
|
|
114
|
+
function formatCodeModeDuration(elapsedMs: number): string {
|
|
115
|
+
if (elapsedMs < 1_000) return `${elapsedMs}ms`;
|
|
116
|
+
if (elapsedMs < 60_000) return `${(elapsedMs / 1_000).toFixed(1)}s`;
|
|
117
|
+
const seconds = Math.floor(elapsedMs / 1_000);
|
|
118
|
+
return `${Math.floor(seconds / 60)}m ${String(seconds % 60).padStart(2, "0")}s`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function pluralizedCodeModeCount(count: number, noun: string): string {
|
|
122
|
+
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function codeModeCellState(
|
|
126
|
+
toolName: CodeModeRenderedToolName,
|
|
127
|
+
details: CodeModeResultDetails,
|
|
128
|
+
): CodeModeCellState {
|
|
129
|
+
if (details.presentation !== undefined) return details.presentation.cell_state;
|
|
130
|
+
if (toolName === "codemode_cancel" && details.result === "success") return "cancelled";
|
|
131
|
+
if (details.result === "pending") return "running";
|
|
132
|
+
if (details.result === "success") return "completed";
|
|
133
|
+
if (details.error.code === "timeout") return "timed_out";
|
|
134
|
+
if (details.error.code === "cancellation") return "cancelled";
|
|
135
|
+
return "failed";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function codeModeSessionLifecycle(
|
|
139
|
+
toolName: CodeModeRenderedToolName,
|
|
140
|
+
details: CodeModeResultDetails,
|
|
141
|
+
): "Session reusable" | "Session closed" | "No reusable Session" {
|
|
142
|
+
if (details.presentation !== undefined) {
|
|
143
|
+
return details.presentation.session_state === "live" ? "Session reusable" : "Session closed";
|
|
144
|
+
}
|
|
145
|
+
if (toolName === "codemode_cancel") return "Session closed";
|
|
146
|
+
if (
|
|
147
|
+
details.result === "failed" &&
|
|
148
|
+
(details.error.code === "capacity" || details.error.code === "unknown")
|
|
149
|
+
) {
|
|
150
|
+
return "No reusable Session";
|
|
151
|
+
}
|
|
152
|
+
if (
|
|
153
|
+
details.result !== "failed" ||
|
|
154
|
+
details.error.code === "script" ||
|
|
155
|
+
details.error.code === "serialization" ||
|
|
156
|
+
details.error.code === "busy"
|
|
157
|
+
) {
|
|
158
|
+
return "Session reusable";
|
|
159
|
+
}
|
|
160
|
+
return "Session closed";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function codeModeValueSummary(value: CodeModeJsonValue | undefined): string {
|
|
164
|
+
if (value === undefined) return "(no data)";
|
|
165
|
+
if (value === null) return "null";
|
|
166
|
+
if (Array.isArray(value)) return `array · ${pluralizedCodeModeCount(value.length, "item")}`;
|
|
167
|
+
if (isCodeModeJsonObject(value)) {
|
|
168
|
+
return `object · ${pluralizedCodeModeCount(Object.keys(value).length, "key")}`;
|
|
169
|
+
}
|
|
170
|
+
if (Value.Check(CodeModeJsonStringSchema, value)) {
|
|
171
|
+
return boundedCodeModePreview(JSON.stringify(value), 48);
|
|
172
|
+
}
|
|
173
|
+
return JSON.stringify(value) ?? "";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function appendCodeModeField(
|
|
177
|
+
container: Container,
|
|
178
|
+
theme: CodeModeRenderTheme,
|
|
179
|
+
label: string,
|
|
180
|
+
value: string | number,
|
|
181
|
+
): void {
|
|
182
|
+
container.addChild(
|
|
183
|
+
new Text(`${theme.fg("muted", `${label}:`)} ${sanitizeCodeModeText(String(value))}`, 0, 0),
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function appendCodeModeBlock(container: Container, language: "ts" | "json", content: string): void {
|
|
188
|
+
const longestFence = Math.max(2, ...[...content.matchAll(/`+/g)].map(([fence]) => fence.length));
|
|
189
|
+
const fence = "`".repeat(longestFence + 1);
|
|
190
|
+
container.addChild(
|
|
191
|
+
new Markdown(`${fence}${language}\n${content}\n${fence}`, 0, 0, getMarkdownTheme()),
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function boundedCodeModeText(text: string, maxLines: number): string {
|
|
196
|
+
const safe = sanitizeCodeModeText(text);
|
|
197
|
+
const truncated = truncateHead(safe, {
|
|
198
|
+
maxBytes: CODEMODE_PRESENTATION_MAX_BYTES,
|
|
199
|
+
maxLines,
|
|
200
|
+
});
|
|
201
|
+
if (!truncated.truncated) return safe;
|
|
202
|
+
const omittedLines = Math.max(0, truncated.totalLines - truncated.outputLines);
|
|
203
|
+
const notice = `… ${pluralizedCodeModeCount(omittedLines, "line")} omitted`;
|
|
204
|
+
return truncated.content.length === 0 ? notice : `${truncated.content}\n${notice}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function renderCodeModeFallback(
|
|
208
|
+
result: AgentToolResult<unknown>,
|
|
209
|
+
options: ToolRenderResultOptions,
|
|
210
|
+
theme: CodeModeRenderTheme,
|
|
211
|
+
isError: boolean,
|
|
212
|
+
): Component {
|
|
213
|
+
const output = sanitizeCodeModeText(
|
|
214
|
+
result.content
|
|
215
|
+
.filter((item) => item.type === "text")
|
|
216
|
+
.map((item) => item.text)
|
|
217
|
+
.join(""),
|
|
218
|
+
);
|
|
219
|
+
const firstLine = output.split("\n").find(Boolean) ?? "CodeMode failed";
|
|
220
|
+
const visible = options.expanded
|
|
221
|
+
? boundedCodeModeText(output, 2_000)
|
|
222
|
+
: boundedCodeModePreview(firstLine, 160);
|
|
223
|
+
const hint =
|
|
224
|
+
!options.expanded && (output.includes("\n") || visible !== firstLine)
|
|
225
|
+
? ` · ${keyText("app.tools.expand")} to expand`
|
|
226
|
+
: "";
|
|
227
|
+
return new Text(theme.fg(isError ? "error" : "toolOutput", `${visible}${hint}`), 0, 0);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function renderCodeModeSummary(
|
|
231
|
+
details: CodeModeResultDetails,
|
|
232
|
+
toolName: CodeModeRenderedToolName,
|
|
233
|
+
theme: CodeModeRenderTheme,
|
|
234
|
+
formatSessionPrefix: CodeModeSessionPrefixFormatter,
|
|
235
|
+
): string {
|
|
236
|
+
const presentation = details.presentation;
|
|
237
|
+
const state = codeModeCellState(toolName, details);
|
|
238
|
+
const status = CODEMODE_STATUS_PRESENTATION[state];
|
|
239
|
+
const activeToolNames = presentation?.active_tool_names.slice(0, 3) ?? [];
|
|
240
|
+
const omittedActiveToolCount = Math.max(
|
|
241
|
+
0,
|
|
242
|
+
(presentation?.active_tool_count ?? 0) - activeToolNames.length,
|
|
243
|
+
);
|
|
244
|
+
const parts = [
|
|
245
|
+
theme.fg(status.color, status.label),
|
|
246
|
+
theme.fg("muted", formatSessionPrefix(details.sessionId)),
|
|
247
|
+
presentation?.cell_ordinal === undefined
|
|
248
|
+
? undefined
|
|
249
|
+
: theme.fg("muted", `Cell ${presentation.cell_ordinal}`),
|
|
250
|
+
state === "running" && presentation !== undefined && presentation.active_tool_names.length > 0
|
|
251
|
+
? theme.fg(
|
|
252
|
+
"muted",
|
|
253
|
+
`${activeToolNames.map((name) => sanitizeCodeModeText(name)).join(", ")}${omittedActiveToolCount > 0 ? ` +${omittedActiveToolCount}` : ""}`,
|
|
254
|
+
)
|
|
255
|
+
: undefined,
|
|
256
|
+
details.result === "success"
|
|
257
|
+
? theme.fg("toolOutput", codeModeValueSummary(details.data))
|
|
258
|
+
: undefined,
|
|
259
|
+
details.result === "failed" ? theme.fg("muted", details.error.code) : undefined,
|
|
260
|
+
details.result === "failed" ? boundedCodeModePreview(details.error.message, 64) : undefined,
|
|
261
|
+
state !== "running" && presentation !== undefined && presentation.nested_tool_count > 0
|
|
262
|
+
? theme.fg("muted", pluralizedCodeModeCount(presentation.nested_tool_count, "tool"))
|
|
263
|
+
: undefined,
|
|
264
|
+
presentation === undefined
|
|
265
|
+
? undefined
|
|
266
|
+
: theme.fg("muted", formatCodeModeDuration(presentation.elapsed_ms)),
|
|
267
|
+
].filter((part): part is string => part !== undefined);
|
|
268
|
+
return parts.join(" ");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Render one CodeMode tool call as a semantic operation with bounded source detail. */
|
|
272
|
+
export function renderCodeModeToolCall(
|
|
273
|
+
toolName: CodeModeRenderedToolName,
|
|
274
|
+
parameters: CodeModeJsonValue,
|
|
275
|
+
theme: CodeModeRenderTheme,
|
|
276
|
+
expanded: boolean,
|
|
277
|
+
formatSessionPrefix: CodeModeSessionPrefixFormatter = shortCodeModeSessionId,
|
|
278
|
+
): Component {
|
|
279
|
+
const operation =
|
|
280
|
+
toolName === "codemode_execute"
|
|
281
|
+
? "Run Cell"
|
|
282
|
+
: toolName === "codemode_result"
|
|
283
|
+
? "Poll"
|
|
284
|
+
: "Cancel";
|
|
285
|
+
const parsedParameters = parseCodeModeRenderedToolParameters(toolName, parameters);
|
|
286
|
+
const executeParameters =
|
|
287
|
+
toolName === "codemode_execute" &&
|
|
288
|
+
parsedParameters !== undefined &&
|
|
289
|
+
"script" in parsedParameters
|
|
290
|
+
? parsedParameters
|
|
291
|
+
: undefined;
|
|
292
|
+
const sessionId = parsedParameters?.sessionId;
|
|
293
|
+
const preview =
|
|
294
|
+
executeParameters === undefined
|
|
295
|
+
? undefined
|
|
296
|
+
: firstMeaningfulScriptLine(executeParameters.script);
|
|
297
|
+
const container = new Container();
|
|
298
|
+
container.addChild(
|
|
299
|
+
new Text(
|
|
300
|
+
[
|
|
301
|
+
theme.fg("toolTitle", theme.bold("CodeMode")),
|
|
302
|
+
theme.fg("accent", operation),
|
|
303
|
+
theme.fg("muted", sessionId === undefined ? "new" : formatSessionPrefix(sessionId)),
|
|
304
|
+
preview === undefined ? undefined : theme.fg("dim", preview),
|
|
305
|
+
]
|
|
306
|
+
.filter((part): part is string => part !== undefined)
|
|
307
|
+
.join(" "),
|
|
308
|
+
0,
|
|
309
|
+
0,
|
|
310
|
+
),
|
|
311
|
+
);
|
|
312
|
+
if (!expanded) return container;
|
|
313
|
+
container.addChild(new Spacer(1));
|
|
314
|
+
if (sessionId !== undefined) appendCodeModeField(container, theme, "Session", sessionId);
|
|
315
|
+
if (executeParameters === undefined) return container;
|
|
316
|
+
if (executeParameters.wait !== undefined)
|
|
317
|
+
appendCodeModeField(container, theme, "Wait", String(executeParameters.wait));
|
|
318
|
+
if (executeParameters.timeoutMs !== undefined)
|
|
319
|
+
appendCodeModeField(container, theme, "Timeout", `${executeParameters.timeoutMs}ms`);
|
|
320
|
+
container.addChild(new Spacer(1));
|
|
321
|
+
container.addChild(new Text(theme.fg("muted", theme.bold("TypeScript")), 0, 0));
|
|
322
|
+
const script = boundedCodeModeText(executeParameters.script, CODEMODE_SCRIPT_MAX_LINES);
|
|
323
|
+
appendCodeModeBlock(container, "ts", script);
|
|
324
|
+
return container;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Render one CodeMode result in collapsed, expanded, partial, or historical form. */
|
|
328
|
+
export function renderCodeModeToolResult(
|
|
329
|
+
toolName: CodeModeRenderedToolName,
|
|
330
|
+
result: AgentToolResult<unknown>,
|
|
331
|
+
options: ToolRenderResultOptions,
|
|
332
|
+
theme: CodeModeRenderTheme,
|
|
333
|
+
parameters: CodeModeRenderedToolParameters,
|
|
334
|
+
isError: boolean,
|
|
335
|
+
formatSessionPrefix: CodeModeSessionPrefixFormatter = shortCodeModeSessionId,
|
|
336
|
+
): Component {
|
|
337
|
+
if (!Value.Check(CodeModeResultDetailsSchema, result.details)) {
|
|
338
|
+
return renderCodeModeFallback(result, options, theme, isError);
|
|
339
|
+
}
|
|
340
|
+
const details = result.details;
|
|
341
|
+
const summary = renderCodeModeSummary(details, toolName, theme, formatSessionPrefix);
|
|
342
|
+
if (!options.expanded) {
|
|
343
|
+
const hint = options.isPartial ? "" : ` · ${keyText("app.tools.expand")} to expand`;
|
|
344
|
+
return new Text(`${summary}${hint}`, 0, 0);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const container = new Container();
|
|
348
|
+
container.addChild(new Text(summary, 0, 0));
|
|
349
|
+
container.addChild(new Spacer(1));
|
|
350
|
+
appendCodeModeField(container, theme, "Session", details.sessionId);
|
|
351
|
+
if (details.presentation?.cell_ordinal !== undefined) {
|
|
352
|
+
appendCodeModeField(container, theme, "Cell", details.presentation.cell_ordinal);
|
|
353
|
+
}
|
|
354
|
+
appendCodeModeField(container, theme, "Lifecycle", codeModeSessionLifecycle(toolName, details));
|
|
355
|
+
|
|
356
|
+
const presentation = details.presentation;
|
|
357
|
+
if (presentation !== undefined && presentation.nested_tools.length > 0) {
|
|
358
|
+
container.addChild(new Spacer(1));
|
|
359
|
+
container.addChild(new Text(theme.fg("muted", theme.bold("Tool activity")), 0, 0));
|
|
360
|
+
for (const nested of presentation.nested_tools.slice(0, 20)) {
|
|
361
|
+
const successful = nested.outcome === "success";
|
|
362
|
+
const symbol = successful ? "✓" : nested.outcome === "cancelled" ? "■" : "×";
|
|
363
|
+
const color: ThemeColor = successful
|
|
364
|
+
? "success"
|
|
365
|
+
: nested.outcome === "cancelled"
|
|
366
|
+
? "warning"
|
|
367
|
+
: "error";
|
|
368
|
+
container.addChild(
|
|
369
|
+
new Text(
|
|
370
|
+
`${theme.fg(color, symbol)} ${sanitizeCodeModeText(nested.name)} ${theme.fg("muted", formatCodeModeDuration(nested.elapsed_ms))}`,
|
|
371
|
+
0,
|
|
372
|
+
0,
|
|
373
|
+
),
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
const omittedNestedToolCount =
|
|
377
|
+
presentation.omitted_nested_tool_count + Math.max(0, presentation.nested_tools.length - 20);
|
|
378
|
+
if (omittedNestedToolCount > 0) {
|
|
379
|
+
container.addChild(
|
|
380
|
+
new Text(
|
|
381
|
+
theme.fg("dim", `… ${pluralizedCodeModeCount(omittedNestedToolCount, "tool")} omitted`),
|
|
382
|
+
0,
|
|
383
|
+
0,
|
|
384
|
+
),
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (details.result === "success") {
|
|
390
|
+
container.addChild(new Spacer(1));
|
|
391
|
+
container.addChild(new Text(theme.fg("muted", theme.bold("Result")), 0, 0));
|
|
392
|
+
if (details.data === undefined)
|
|
393
|
+
container.addChild(new Text(theme.fg("dim", "(no data)"), 0, 0));
|
|
394
|
+
else {
|
|
395
|
+
const json = boundedCodeModeText(formatCodeModePresentationData(details.data), 2_000);
|
|
396
|
+
appendCodeModeBlock(container, "json", json);
|
|
397
|
+
}
|
|
398
|
+
} else if (details.result === "failed") {
|
|
399
|
+
container.addChild(new Spacer(1));
|
|
400
|
+
container.addChild(new Text(theme.fg("muted", theme.bold("Error")), 0, 0));
|
|
401
|
+
appendCodeModeField(container, theme, "Code", details.error.code satisfies CodeModeErrorCode);
|
|
402
|
+
container.addChild(
|
|
403
|
+
new Text(theme.fg("error", boundedCodeModeText(details.error.message, 2_000)), 0, 0),
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
if (presentation?.spill_path !== undefined) {
|
|
407
|
+
appendCodeModeField(container, theme, "Result Spill", presentation.spill_path);
|
|
408
|
+
}
|
|
409
|
+
return container;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** Create the three CodeMode tools with semantic call and result Transcript renderers. */
|
|
413
|
+
export function createRenderedCodeModeToolDefinitions(
|
|
414
|
+
operations: CodeModeToolOperations,
|
|
415
|
+
executeDescription?: string,
|
|
416
|
+
formatSessionPrefix: CodeModeSessionPrefixFormatter = shortCodeModeSessionId,
|
|
417
|
+
): ReturnType<typeof createCodeModeToolDefinitions> {
|
|
418
|
+
const [executeTool, resultTool, cancelTool] = createCodeModeToolDefinitions(
|
|
419
|
+
operations,
|
|
420
|
+
executeDescription,
|
|
421
|
+
);
|
|
422
|
+
return [
|
|
423
|
+
{
|
|
424
|
+
...executeTool,
|
|
425
|
+
renderCall: (args, theme, context) =>
|
|
426
|
+
renderCodeModeToolCall(
|
|
427
|
+
"codemode_execute",
|
|
428
|
+
args,
|
|
429
|
+
theme,
|
|
430
|
+
context.expanded,
|
|
431
|
+
formatSessionPrefix,
|
|
432
|
+
),
|
|
433
|
+
renderResult: (result, options, theme, context) =>
|
|
434
|
+
renderCodeModeToolResult(
|
|
435
|
+
"codemode_execute",
|
|
436
|
+
result,
|
|
437
|
+
options,
|
|
438
|
+
theme,
|
|
439
|
+
context.args,
|
|
440
|
+
context.isError,
|
|
441
|
+
formatSessionPrefix,
|
|
442
|
+
),
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
...resultTool,
|
|
446
|
+
renderCall: (args, theme, context) =>
|
|
447
|
+
renderCodeModeToolCall(
|
|
448
|
+
"codemode_result",
|
|
449
|
+
args,
|
|
450
|
+
theme,
|
|
451
|
+
context.expanded,
|
|
452
|
+
formatSessionPrefix,
|
|
453
|
+
),
|
|
454
|
+
renderResult: (result, options, theme, context) =>
|
|
455
|
+
renderCodeModeToolResult(
|
|
456
|
+
"codemode_result",
|
|
457
|
+
result,
|
|
458
|
+
options,
|
|
459
|
+
theme,
|
|
460
|
+
context.args,
|
|
461
|
+
context.isError,
|
|
462
|
+
formatSessionPrefix,
|
|
463
|
+
),
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
...cancelTool,
|
|
467
|
+
renderCall: (args, theme, context) =>
|
|
468
|
+
renderCodeModeToolCall(
|
|
469
|
+
"codemode_cancel",
|
|
470
|
+
args,
|
|
471
|
+
theme,
|
|
472
|
+
context.expanded,
|
|
473
|
+
formatSessionPrefix,
|
|
474
|
+
),
|
|
475
|
+
renderResult: (result, options, theme, context) =>
|
|
476
|
+
renderCodeModeToolResult(
|
|
477
|
+
"codemode_cancel",
|
|
478
|
+
result,
|
|
479
|
+
options,
|
|
480
|
+
theme,
|
|
481
|
+
context.args,
|
|
482
|
+
context.isError,
|
|
483
|
+
formatSessionPrefix,
|
|
484
|
+
),
|
|
485
|
+
},
|
|
486
|
+
];
|
|
487
|
+
}
|