@aexol/spectral 0.7.6 → 0.7.8

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.
Files changed (57) hide show
  1. package/dist/agent/index.js +16 -140
  2. package/dist/cli.js +25 -220
  3. package/dist/extensions/spectral-vision-fallback.js +188 -0
  4. package/dist/memory/commands/status.js +5 -5
  5. package/dist/memory/commands/view.js +16 -14
  6. package/dist/memory/compaction.js +31 -3
  7. package/dist/memory/prompts.js +5 -5
  8. package/dist/memory/tools/recall-observation.js +2 -2
  9. package/dist/pi/coding-agent/config.js +0 -11
  10. package/dist/pi/coding-agent/core/agent-session.js +3 -17
  11. package/dist/pi/coding-agent/core/extensions/loader.js +0 -6
  12. package/dist/pi/coding-agent/core/extensions/runner.js +7 -1
  13. package/dist/pi/coding-agent/core/keybindings.js +129 -2
  14. package/dist/pi/coding-agent/core/settings-manager.js +20 -0
  15. package/dist/pi/coding-agent/core/tools/bash.js +17 -63
  16. package/dist/pi/coding-agent/core/tools/edit.js +4 -141
  17. package/dist/pi/coding-agent/core/tools/find.js +0 -11
  18. package/dist/pi/coding-agent/core/tools/grep.js +0 -11
  19. package/dist/pi/coding-agent/core/tools/ls.js +0 -11
  20. package/dist/pi/coding-agent/core/tools/read.js +0 -12
  21. package/dist/pi/coding-agent/core/tools/render-utils.js +1 -14
  22. package/dist/pi/coding-agent/core/tools/write.js +2 -97
  23. package/dist/pi/coding-agent/modes/interactive/components/keybinding-hints.js +1 -1
  24. package/dist/pi/coding-agent/modes/interactive/components/visual-truncate.js +6 -12
  25. package/dist/pi/coding-agent/modes/interactive/theme/theme.js +1 -2
  26. package/dist/relay/models-fetch.js +13 -1
  27. package/dist/server/pi-bridge.js +57 -4
  28. package/dist/server/session-stream.js +7 -1
  29. package/package.json +1 -1
  30. package/dist/pi/coding-agent/core/export-html/ansi-to-html.js +0 -248
  31. package/dist/pi/coding-agent/core/export-html/index.js +0 -225
  32. package/dist/pi/coding-agent/core/export-html/tool-renderer.js +0 -107
  33. package/dist/pi/tui/autocomplete.js +0 -631
  34. package/dist/pi/tui/components/box.js +0 -103
  35. package/dist/pi/tui/components/cancellable-loader.js +0 -34
  36. package/dist/pi/tui/components/editor.js +0 -1915
  37. package/dist/pi/tui/components/image.js +0 -88
  38. package/dist/pi/tui/components/input.js +0 -425
  39. package/dist/pi/tui/components/loader.js +0 -68
  40. package/dist/pi/tui/components/markdown.js +0 -633
  41. package/dist/pi/tui/components/select-list.js +0 -158
  42. package/dist/pi/tui/components/settings-list.js +0 -184
  43. package/dist/pi/tui/components/spacer.js +0 -22
  44. package/dist/pi/tui/components/text.js +0 -88
  45. package/dist/pi/tui/components/truncated-text.js +0 -50
  46. package/dist/pi/tui/editor-component.js +0 -1
  47. package/dist/pi/tui/fuzzy.js +0 -109
  48. package/dist/pi/tui/index.js +0 -31
  49. package/dist/pi/tui/keybindings.js +0 -173
  50. package/dist/pi/tui/keys.js +0 -1172
  51. package/dist/pi/tui/kill-ring.js +0 -43
  52. package/dist/pi/tui/stdin-buffer.js +0 -360
  53. package/dist/pi/tui/terminal-image.js +0 -335
  54. package/dist/pi/tui/terminal.js +0 -324
  55. package/dist/pi/tui/tui.js +0 -1076
  56. package/dist/pi/tui/undo-stack.js +0 -24
  57. package/dist/pi/tui/utils.js +0 -1016
@@ -1,7 +1,134 @@
1
- import { TUI_KEYBINDINGS, KeybindingsManager as TuiKeybindingsManager, } from "../../tui/index.js";
2
1
  import { existsSync, readFileSync } from "fs";
3
2
  import { join } from "path";
4
3
  import { getAgentDir } from "../config.js";
4
+ /** Placeholder — no terminal key input in headless mode. */
5
+ export function matchesKey(_data, _keyId) {
6
+ return false;
7
+ }
8
+ /** Placeholder. */
9
+ export function parseKey(_data) {
10
+ return "";
11
+ }
12
+ /** Placeholder. */
13
+ export function isKeyRelease(_data) {
14
+ return false;
15
+ }
16
+ /** Placeholder. */
17
+ export function isKeyRepeat(_data) {
18
+ return false;
19
+ }
20
+ /** Placeholder. */
21
+ export function isKittyProtocolActive() {
22
+ return false;
23
+ }
24
+ /** Placeholder. */
25
+ export function setKittyProtocolActive(_active) { }
26
+ /** Placeholder. */
27
+ export function decodeKittyPrintable(_data) {
28
+ return _data;
29
+ }
30
+ /** Key identifier helper. */
31
+ export class Key {
32
+ static c(key) { return `ctrl+${key}`; }
33
+ static m(key) { return `alt+${key}`; }
34
+ static cm(key) { return `ctrl+alt+${key}`; }
35
+ static backspace = "backspace";
36
+ static enter = "enter";
37
+ static escape = "escape";
38
+ static tab = "tab";
39
+ static up = "up";
40
+ static down = "down";
41
+ static left = "left";
42
+ static right = "right";
43
+ static delete = "delete";
44
+ static home = "home";
45
+ static end = "end";
46
+ static pageUp = "pageUp";
47
+ static pageDown = "pageDown";
48
+ static f1 = "f1";
49
+ static f2 = "f2";
50
+ static f3 = "f3";
51
+ static f4 = "f4";
52
+ static f5 = "f5";
53
+ static f6 = "f6";
54
+ static f7 = "f7";
55
+ static f8 = "f8";
56
+ static f9 = "f9";
57
+ static f10 = "f10";
58
+ static f11 = "f11";
59
+ static f12 = "f12";
60
+ static space = "space";
61
+ }
62
+ /** Built-in keybinding defaults. */
63
+ export const TUI_KEYBINDINGS = {
64
+ "tui.editor.cursorUp": { defaultKeys: "up" },
65
+ "tui.editor.cursorDown": { defaultKeys: "down" },
66
+ "tui.editor.cursorLeft": { defaultKeys: "left" },
67
+ "tui.editor.cursorRight": { defaultKeys: "right" },
68
+ "tui.editor.cursorWordLeft": { defaultKeys: ["ctrl+left", "alt+left"] },
69
+ "tui.editor.cursorWordRight": { defaultKeys: ["ctrl+right", "alt+right"] },
70
+ "tui.editor.cursorLineStart": { defaultKeys: "home" },
71
+ "tui.editor.cursorLineEnd": { defaultKeys: "end" },
72
+ "tui.editor.jumpForward": { defaultKeys: [] },
73
+ "tui.editor.jumpBackward": { defaultKeys: [] },
74
+ "tui.editor.pageUp": { defaultKeys: "pageUp" },
75
+ "tui.editor.pageDown": { defaultKeys: "pageDown" },
76
+ "tui.editor.deleteCharBackward": { defaultKeys: "backspace" },
77
+ "tui.editor.deleteCharForward": { defaultKeys: "delete" },
78
+ "tui.editor.deleteWordBackward": { defaultKeys: ["ctrl+backspace", "alt+backspace"] },
79
+ "tui.editor.deleteWordForward": { defaultKeys: ["ctrl+delete", "alt+delete"] },
80
+ "tui.editor.deleteToLineStart": { defaultKeys: "ctrl+u" },
81
+ "tui.editor.deleteToLineEnd": { defaultKeys: "ctrl+k" },
82
+ "tui.editor.yank": { defaultKeys: "ctrl+y" },
83
+ "tui.editor.yankPop": { defaultKeys: "alt+y" },
84
+ "tui.editor.undo": { defaultKeys: "ctrl+_" },
85
+ "tui.input.newLine": { defaultKeys: "enter" },
86
+ "tui.input.submit": { defaultKeys: [] },
87
+ "tui.input.tab": { defaultKeys: "tab" },
88
+ "tui.input.copy": { defaultKeys: [] },
89
+ "tui.select.up": { defaultKeys: "up" },
90
+ "tui.select.down": { defaultKeys: "down" },
91
+ "tui.select.pageUp": { defaultKeys: "pageUp" },
92
+ "tui.select.pageDown": { defaultKeys: "pageDown" },
93
+ "tui.select.confirm": { defaultKeys: "enter" },
94
+ "tui.select.cancel": { defaultKeys: "escape" },
95
+ };
96
+ // ============================================================================
97
+ // Base KeybindingsManager
98
+ // ============================================================================
99
+ export class BaseKeybindingsManager {
100
+ definitions;
101
+ userBindings;
102
+ constructor(definitions = {}, userBindings = {}) {
103
+ this.definitions = definitions;
104
+ this.userBindings = userBindings;
105
+ }
106
+ getKeys(binding) {
107
+ const key = binding;
108
+ const def = this.definitions[key];
109
+ if (!def)
110
+ return [];
111
+ return Array.isArray(def.defaultKeys) ? def.defaultKeys : [def.defaultKeys];
112
+ }
113
+ setUserBindings(bindings) {
114
+ this.userBindings = bindings;
115
+ }
116
+ getResolvedBindings() {
117
+ return { ...this.userBindings };
118
+ }
119
+ reconfigure(_config) { }
120
+ }
121
+ // ============================================================================
122
+ // Singleton accessor
123
+ // ============================================================================
124
+ let _globalKeybindings = null;
125
+ export function getKeybindings() {
126
+ if (!_globalKeybindings) {
127
+ _globalKeybindings = KeybindingsManager.create();
128
+ }
129
+ return _globalKeybindings;
130
+ }
131
+ export function setKeybindings(_config) { }
5
132
  export const KEYBINDINGS = {
6
133
  ...TUI_KEYBINDINGS,
7
134
  "app.interrupt": { defaultKeys: "escape", description: "Cancel or abort" },
@@ -266,7 +393,7 @@ function loadRawConfig(path) {
266
393
  return undefined;
267
394
  }
268
395
  }
269
- export class KeybindingsManager extends TuiKeybindingsManager {
396
+ export class KeybindingsManager extends BaseKeybindingsManager {
270
397
  configPath;
271
398
  constructor(userBindings = {}, configPath) {
272
399
  super(KEYBINDINGS, userBindings);
@@ -385,6 +385,26 @@ export class SettingsManager {
385
385
  getDefaultModel() {
386
386
  return this.settings.defaultModel;
387
387
  }
388
+ getDefaultVisionProvider() {
389
+ return this.settings.defaultVisionProvider;
390
+ }
391
+ getDefaultVisionModel() {
392
+ return this.settings.defaultVisionModel;
393
+ }
394
+ setDefaultVisionProvider(provider) {
395
+ this.globalSettings.defaultVisionProvider = provider;
396
+ this.markModified("defaultVisionProvider");
397
+ }
398
+ setDefaultVisionModel(modelId) {
399
+ this.globalSettings.defaultVisionModel = modelId;
400
+ this.markModified("defaultVisionModel");
401
+ }
402
+ setDefaultVisionModelAndProvider(provider, modelId) {
403
+ this.globalSettings.defaultVisionProvider = provider;
404
+ this.globalSettings.defaultVisionModel = modelId;
405
+ this.markModified("defaultVisionProvider");
406
+ this.markModified("defaultVisionModel");
407
+ }
388
408
  setDefaultProvider(provider) {
389
409
  this.globalSettings.defaultProvider = provider;
390
410
  this.markModified("defaultProvider");
@@ -1,9 +1,7 @@
1
1
  import { existsSync } from "node:fs";
2
- import { Container, Text, truncateToWidth } from "../../../tui/index.js";
3
2
  import { spawn } from "child_process";
4
3
  import { Type } from "typebox";
5
4
  import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
6
- import { truncateToVisualLines } from "../../modes/interactive/components/visual-truncate.js";
7
5
  import { theme } from "../../modes/interactive/theme/theme.js";
8
6
  import { waitForChildProcess } from "../../utils/child-process.js";
9
7
  import { getShellConfig, getShellEnv, killProcessTree, trackDetachedChildPid, untrackDetachedChildPid, } from "../../utils/shell.js";
@@ -102,13 +100,6 @@ function resolveSpawnContext(command, cwd, spawnHook) {
102
100
  }
103
101
  const BASH_PREVIEW_LINES = 5;
104
102
  const BASH_UPDATE_THROTTLE_MS = 100;
105
- class BashResultRenderComponent extends Container {
106
- state = {
107
- cachedWidth: undefined,
108
- cachedLines: undefined,
109
- cachedSkipped: undefined,
110
- };
111
- }
112
103
  function formatDuration(ms) {
113
104
  return `${(ms / 1000).toFixed(1)}s`;
114
105
  }
@@ -119,9 +110,7 @@ function formatBashCall(args) {
119
110
  const commandDisplay = command === null ? invalidArgText(theme) : command ? command : theme.fg("toolOutput", "...");
120
111
  return theme.fg("toolTitle", theme.bold(`$ ${commandDisplay}`)) + timeoutSuffix;
121
112
  }
122
- function rebuildBashResultRenderComponent(component, result, options, showImages, startedAt, endedAt) {
123
- const state = component.state;
124
- component.clear();
113
+ function formatBashResult(result, options, showImages, startedAt, endedAt) {
125
114
  let output = getTextOutput(result, showImages).trim();
126
115
  const truncation = result.details?.truncation;
127
116
  const fullOutputPath = result.details?.fullOutputPath;
@@ -131,36 +120,27 @@ function rebuildBashResultRenderComponent(component, result, options, showImages
131
120
  output = output.slice(0, footerStart).trimEnd();
132
121
  }
133
122
  }
123
+ const parts = [];
134
124
  if (output) {
135
125
  const styledOutput = output
136
126
  .split("\n")
137
127
  .map((line) => theme.fg("toolOutput", line))
138
128
  .join("\n");
139
129
  if (options.expanded) {
140
- component.addChild(new Text(`\n${styledOutput}`, 0, 0));
130
+ parts.push(`\n${styledOutput}`);
141
131
  }
142
132
  else {
143
- component.addChild({
144
- render: (width) => {
145
- if (state.cachedLines === undefined || state.cachedWidth !== width) {
146
- const preview = truncateToVisualLines(styledOutput, BASH_PREVIEW_LINES, width);
147
- state.cachedLines = preview.visualLines;
148
- state.cachedSkipped = preview.skippedCount;
149
- state.cachedWidth = width;
150
- }
151
- if (state.cachedSkipped && state.cachedSkipped > 0) {
152
- const hint = theme.fg("muted", `... (${state.cachedSkipped} earlier lines,`) +
153
- ` ${keyHint("app.tools.expand", "to expand")})`;
154
- return ["", truncateToWidth(hint, width, "..."), ...(state.cachedLines ?? [])];
155
- }
156
- return ["", ...(state.cachedLines ?? [])];
157
- },
158
- invalidate: () => {
159
- state.cachedWidth = undefined;
160
- state.cachedLines = undefined;
161
- state.cachedSkipped = undefined;
162
- },
163
- });
133
+ const styledLines = styledOutput.split("\n");
134
+ if (styledLines.length > BASH_PREVIEW_LINES) {
135
+ const hint = theme.fg("muted", `... (${styledLines.length - BASH_PREVIEW_LINES} earlier lines, ${keyHint("app.tools.expand", "to expand")})`);
136
+ parts.push("");
137
+ parts.push(hint);
138
+ parts.push(...styledLines.slice(-BASH_PREVIEW_LINES));
139
+ }
140
+ else {
141
+ parts.push("");
142
+ parts.push(...styledLines);
143
+ }
164
144
  }
165
145
  }
166
146
  if (truncation?.truncated || fullOutputPath) {
@@ -176,13 +156,14 @@ function rebuildBashResultRenderComponent(component, result, options, showImages
176
156
  warnings.push(`Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)`);
177
157
  }
178
158
  }
179
- component.addChild(new Text(`\n${theme.fg("warning", `[${warnings.join(". ")}]`)}`, 0, 0));
159
+ parts.push(`\n${theme.fg("warning", `[${warnings.join(". ")}]`)}`);
180
160
  }
181
161
  if (startedAt !== undefined) {
182
162
  const label = options.isPartial ? "Elapsed" : "Took";
183
163
  const endTime = endedAt ?? Date.now();
184
- component.addChild(new Text(`\n${theme.fg("muted", `${label} ${formatDuration(endTime - startedAt)}`)}`, 0, 0));
164
+ parts.push(`\n${theme.fg("muted", `${label} ${formatDuration(endTime - startedAt)}`)}`);
185
165
  }
166
+ return parts.join("");
186
167
  }
187
168
  export function createBashToolDefinition(cwd, options) {
188
169
  const ops = options?.operations ?? createLocalBashOperations({ shellPath: options?.shellPath });
@@ -307,33 +288,6 @@ export function createBashToolDefinition(cwd, options) {
307
288
  clearUpdateTimer();
308
289
  }
309
290
  },
310
- renderCall(args, _theme, context) {
311
- const state = context.state;
312
- if (context.executionStarted && state.startedAt === undefined) {
313
- state.startedAt = Date.now();
314
- state.endedAt = undefined;
315
- }
316
- const text = context.lastComponent ?? new Text("", 0, 0);
317
- text.setText(formatBashCall(args));
318
- return text;
319
- },
320
- renderResult(result, options, _theme, context) {
321
- const state = context.state;
322
- if (state.startedAt !== undefined && options.isPartial && !state.interval) {
323
- state.interval = setInterval(() => context.invalidate(), 1000);
324
- }
325
- if (!options.isPartial || context.isError) {
326
- state.endedAt ??= Date.now();
327
- if (state.interval) {
328
- clearInterval(state.interval);
329
- state.interval = undefined;
330
- }
331
- }
332
- const component = context.lastComponent ?? new BashResultRenderComponent();
333
- rebuildBashResultRenderComponent(component, result, options, context.showImages, state.startedAt, state.endedAt);
334
- component.invalidate();
335
- return component;
336
- },
337
291
  };
338
292
  }
339
293
  export function createBashTool(cwd, options) {
@@ -1,9 +1,8 @@
1
- import { Box, Container, Spacer, Text } from "../../../tui/index.js";
2
1
  import { constants } from "fs";
3
2
  import { access as fsAccess, readFile as fsReadFile, writeFile as fsWriteFile } from "fs/promises";
4
3
  import { Type } from "typebox";
5
4
  import { renderDiff } from "../../modes/interactive/components/diff.js";
6
- import { applyEditsToNormalizedContent, computeEditsDiff, detectLineEnding, generateDiffString, generateUnifiedPatch, normalizeToLF, restoreLineEndings, stripBom, } from "./edit-diff.js";
5
+ import { applyEditsToNormalizedContent, detectLineEnding, generateDiffString, generateUnifiedPatch, normalizeToLF, restoreLineEndings, stripBom, } from "./edit-diff.js";
7
6
  import { withFileMutationQueue } from "./file-mutation-queue.js";
8
7
  import { resolveToCwd } from "./path-utils.js";
9
8
  import { invalidArgText, shortenPath, str } from "./render-utils.js";
@@ -54,45 +53,6 @@ function validateEditInput(input) {
54
53
  }
55
54
  return { path: input.path, edits: input.edits };
56
55
  }
57
- function createEditCallRenderComponent() {
58
- return Object.assign(new Box(1, 1, (text) => text), {
59
- preview: undefined,
60
- previewArgsKey: undefined,
61
- previewPending: false,
62
- settledError: false,
63
- });
64
- }
65
- function getEditCallRenderComponent(state, lastComponent) {
66
- if (lastComponent instanceof Box) {
67
- const component = lastComponent;
68
- state.callComponent = component;
69
- return component;
70
- }
71
- if (state.callComponent) {
72
- return state.callComponent;
73
- }
74
- const component = createEditCallRenderComponent();
75
- state.callComponent = component;
76
- return component;
77
- }
78
- function getRenderablePreviewInput(args) {
79
- if (!args) {
80
- return null;
81
- }
82
- const path = typeof args.path === "string" ? args.path : typeof args.file_path === "string" ? args.file_path : null;
83
- if (!path) {
84
- return null;
85
- }
86
- if (Array.isArray(args.edits) &&
87
- args.edits.length > 0 &&
88
- args.edits.every((edit) => typeof edit?.oldText === "string" && typeof edit?.newText === "string")) {
89
- return { path, edits: args.edits };
90
- }
91
- if (typeof args.oldText === "string" && typeof args.newText === "string") {
92
- return { path, edits: [{ oldText: args.oldText, newText: args.newText }] };
93
- }
94
- return null;
95
- }
96
56
  function formatEditCall(args, theme) {
97
57
  const invalidArg = invalidArgText(theme);
98
58
  const rawPath = str(args?.file_path ?? args?.path);
@@ -100,64 +60,24 @@ function formatEditCall(args, theme) {
100
60
  const pathDisplay = path === null ? invalidArg : path ? theme.fg("accent", path) : theme.fg("toolOutput", "...");
101
61
  return `${theme.fg("toolTitle", theme.bold("edit"))} ${pathDisplay}`;
102
62
  }
103
- function formatEditResult(args, preview, result, theme, isError) {
63
+ function formatEditResult(args, result, theme, isError) {
104
64
  const rawPath = str(args?.file_path ?? args?.path);
105
- const previewDiff = preview && !("error" in preview) ? preview.diff : undefined;
106
- const previewError = preview && "error" in preview ? preview.error : undefined;
107
65
  if (isError) {
108
66
  const errorText = result.content
109
67
  .filter((c) => c.type === "text")
110
68
  .map((c) => c.text || "")
111
69
  .join("\n");
112
- if (!errorText || errorText === previewError) {
70
+ if (!errorText) {
113
71
  return undefined;
114
72
  }
115
73
  return theme.fg("error", errorText);
116
74
  }
117
75
  const resultDiff = result.details?.diff;
118
- if (resultDiff && resultDiff !== previewDiff) {
76
+ if (resultDiff) {
119
77
  return renderDiff(resultDiff, { filePath: rawPath ?? undefined });
120
78
  }
121
79
  return undefined;
122
80
  }
123
- function getEditHeaderBg(preview, settledError, theme) {
124
- if (preview) {
125
- if ("error" in preview) {
126
- return (text) => theme.bg("toolErrorBg", text);
127
- }
128
- return (text) => theme.bg("toolSuccessBg", text);
129
- }
130
- if (settledError) {
131
- return (text) => theme.bg("toolErrorBg", text);
132
- }
133
- return (text) => theme.bg("toolPendingBg", text);
134
- }
135
- function buildEditCallComponent(component, args, theme) {
136
- component.setBgFn(getEditHeaderBg(component.preview, component.settledError, theme));
137
- component.clear();
138
- component.addChild(new Text(formatEditCall(args, theme), 0, 0));
139
- if (!component.preview) {
140
- return component;
141
- }
142
- const body = "error" in component.preview ? theme.fg("error", component.preview.error) : renderDiff(component.preview.diff);
143
- component.addChild(new Spacer(1));
144
- component.addChild(new Text(body, 0, 0));
145
- return component;
146
- }
147
- function setEditPreview(component, preview, argsKey) {
148
- const current = component.preview;
149
- const changed = current === undefined ||
150
- ("error" in current && "error" in preview
151
- ? current.error !== preview.error
152
- : "error" in current !== "error" in preview) ||
153
- (!("error" in current) &&
154
- !("error" in preview) &&
155
- (current.diff !== preview.diff || current.firstChangedLine !== preview.firstChangedLine));
156
- component.preview = preview;
157
- component.previewArgsKey = argsKey;
158
- component.previewPending = false;
159
- return changed;
160
- }
161
81
  export function createEditToolDefinition(cwd, options) {
162
82
  const ops = options?.operations ?? defaultEditOperations;
163
83
  return {
@@ -172,7 +92,6 @@ export function createEditToolDefinition(cwd, options) {
172
92
  "Keep edits[].oldText as small as possible while still being unique in the file. Do not pad with large unchanged regions.",
173
93
  ],
174
94
  parameters: editSchema,
175
- renderShell: "self",
176
95
  prepareArguments: prepareEditArguments,
177
96
  async execute(_toolCallId, input, signal, _onUpdate, _ctx) {
178
97
  const { path, edits } = validateEditInput(input);
@@ -261,62 +180,6 @@ export function createEditToolDefinition(cwd, options) {
261
180
  })();
262
181
  }));
263
182
  },
264
- renderCall(args, theme, context) {
265
- const component = getEditCallRenderComponent(context.state, context.lastComponent);
266
- const previewInput = getRenderablePreviewInput(args);
267
- const argsKey = previewInput
268
- ? JSON.stringify({ path: previewInput.path, edits: previewInput.edits })
269
- : undefined;
270
- if (component.previewArgsKey !== argsKey) {
271
- component.preview = undefined;
272
- component.previewArgsKey = argsKey;
273
- component.previewPending = false;
274
- component.settledError = false;
275
- }
276
- if (context.argsComplete && previewInput && !component.preview && !component.previewPending) {
277
- component.previewPending = true;
278
- const requestKey = argsKey;
279
- void computeEditsDiff(previewInput.path, previewInput.edits, context.cwd).then((preview) => {
280
- if (component.previewArgsKey === requestKey) {
281
- setEditPreview(component, preview, requestKey);
282
- context.invalidate();
283
- }
284
- });
285
- }
286
- return buildEditCallComponent(component, args, theme);
287
- },
288
- renderResult(result, _options, theme, context) {
289
- const callComponent = context.state.callComponent;
290
- const previewInput = getRenderablePreviewInput(context.args);
291
- const argsKey = previewInput
292
- ? JSON.stringify({ path: previewInput.path, edits: previewInput.edits })
293
- : undefined;
294
- const typedResult = result;
295
- const resultDiff = !context.isError ? typedResult.details?.diff : undefined;
296
- let changed = false;
297
- if (callComponent) {
298
- if (typeof resultDiff === "string") {
299
- changed =
300
- setEditPreview(callComponent, { diff: resultDiff, firstChangedLine: typedResult.details?.firstChangedLine }, argsKey) || changed;
301
- }
302
- if (callComponent.settledError !== context.isError) {
303
- callComponent.settledError = context.isError;
304
- changed = true;
305
- }
306
- if (changed) {
307
- buildEditCallComponent(callComponent, context.args, theme);
308
- }
309
- }
310
- const output = formatEditResult(context.args, callComponent?.preview, typedResult, theme, context.isError);
311
- const component = context.lastComponent ?? new Container();
312
- component.clear();
313
- if (!output) {
314
- return component;
315
- }
316
- component.addChild(new Spacer(1));
317
- component.addChild(new Text(output, 1, 0));
318
- return component;
319
- },
320
183
  };
321
184
  }
322
185
  export function createEditTool(cwd, options) {
@@ -1,5 +1,4 @@
1
1
  import { createInterface } from "node:readline";
2
- import { Text } from "../../../tui/index.js";
3
2
  import { spawn } from "child_process";
4
3
  import { existsSync } from "fs";
5
4
  import path from "path";
@@ -280,16 +279,6 @@ export function createFindToolDefinition(cwd, options) {
280
279
  })();
281
280
  });
282
281
  },
283
- renderCall(args, theme, context) {
284
- const text = context.lastComponent ?? new Text("", 0, 0);
285
- text.setText(formatFindCall(args, theme));
286
- return text;
287
- },
288
- renderResult(result, options, theme, context) {
289
- const text = context.lastComponent ?? new Text("", 0, 0);
290
- text.setText(formatFindResult(result, options, theme, context.showImages));
291
- return text;
292
- },
293
282
  };
294
283
  }
295
284
  export function createFindTool(cwd, options) {
@@ -1,5 +1,4 @@
1
1
  import { createInterface } from "node:readline";
2
- import { Text } from "../../../tui/index.js";
3
2
  import { spawn } from "child_process";
4
3
  import { readFileSync, statSync } from "fs";
5
4
  import path from "path";
@@ -286,16 +285,6 @@ export function createGrepToolDefinition(cwd, options) {
286
285
  })();
287
286
  });
288
287
  },
289
- renderCall(args, theme, context) {
290
- const text = context.lastComponent ?? new Text("", 0, 0);
291
- text.setText(formatGrepCall(args, theme));
292
- return text;
293
- },
294
- renderResult(result, options, theme, context) {
295
- const text = context.lastComponent ?? new Text("", 0, 0);
296
- text.setText(formatGrepResult(result, options, theme, context.showImages));
297
- return text;
298
- },
299
288
  };
300
289
  }
301
290
  export function createGrepTool(cwd, options) {
@@ -1,4 +1,3 @@
1
- import { Text } from "../../../tui/index.js";
2
1
  import { existsSync, readdirSync, statSync } from "fs";
3
2
  import nodePath from "path";
4
3
  import { Type } from "typebox";
@@ -151,16 +150,6 @@ export function createLsToolDefinition(cwd, options) {
151
150
  })();
152
151
  });
153
152
  },
154
- renderCall(args, theme, context) {
155
- const text = context.lastComponent ?? new Text("", 0, 0);
156
- text.setText(formatLsCall(args, theme));
157
- return text;
158
- },
159
- renderResult(result, options, theme, context) {
160
- const text = context.lastComponent ?? new Text("", 0, 0);
161
- text.setText(formatLsResult(result, options, theme, context.showImages));
162
- return text;
163
- },
164
153
  };
165
154
  }
166
155
  export function createLsTool(cwd, options) {
@@ -1,5 +1,4 @@
1
1
  import { basename, dirname, isAbsolute, relative, resolve as resolvePath, sep } from "node:path";
2
- import { Text } from "../../../tui/index.js";
3
2
  import { constants } from "fs";
4
3
  import { access as fsAccess, readFile as fsReadFile } from "fs/promises";
5
4
  import { Type } from "typebox";
@@ -270,17 +269,6 @@ export function createReadToolDefinition(cwd, options) {
270
269
  })();
271
270
  });
272
271
  },
273
- renderCall(args, theme, context) {
274
- const text = context.lastComponent ?? new Text("", 0, 0);
275
- const classification = !context.expanded ? getCompactReadClassification(args, context.cwd) : undefined;
276
- text.setText(classification ? formatCompactReadCall(classification, args, theme) : formatReadCall(args, theme));
277
- return text;
278
- },
279
- renderResult(result, options, theme, context) {
280
- const text = context.lastComponent ?? new Text("", 0, 0);
281
- text.setText(formatReadResult(context.args, result, options, theme, context.showImages, context.cwd, context.isError));
282
- return text;
283
- },
284
272
  };
285
273
  }
286
274
  export function createReadTool(cwd, options) {
@@ -1,5 +1,4 @@
1
1
  import * as os from "node:os";
2
- import { getCapabilities, getImageDimensions, imageFallback } from "../../../tui/index.js";
3
2
  import { stripAnsi } from "../../utils/ansi.js";
4
3
  import { sanitizeBinaryOutput } from "../../utils/shell.js";
5
4
  export function shortenPath(path) {
@@ -24,23 +23,11 @@ export function replaceTabs(text) {
24
23
  export function normalizeDisplayText(text) {
25
24
  return text.replace(/\r/g, "");
26
25
  }
27
- export function getTextOutput(result, showImages) {
26
+ export function getTextOutput(result, _showImages) {
28
27
  if (!result)
29
28
  return "";
30
29
  const textBlocks = result.content.filter((c) => c.type === "text");
31
- const imageBlocks = result.content.filter((c) => c.type === "image");
32
30
  let output = textBlocks.map((c) => sanitizeBinaryOutput(stripAnsi(c.text || "")).replace(/\r/g, "")).join("\n");
33
- const caps = getCapabilities();
34
- if (imageBlocks.length > 0 && (!caps.images || !showImages)) {
35
- const imageIndicators = imageBlocks
36
- .map((img) => {
37
- const mimeType = img.mimeType ?? "image/unknown";
38
- const dims = img.data && img.mimeType ? (getImageDimensions(img.data, img.mimeType) ?? undefined) : undefined;
39
- return imageFallback(mimeType, dims);
40
- })
41
- .join("\n");
42
- output = output ? `${output}\n${imageIndicators}` : imageIndicators;
43
- }
44
31
  return output;
45
32
  }
46
33
  export function invalidArgText(theme) {