@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.
- package/dist/agent/index.js +16 -140
- package/dist/cli.js +25 -220
- package/dist/extensions/spectral-vision-fallback.js +188 -0
- package/dist/memory/commands/status.js +5 -5
- package/dist/memory/commands/view.js +16 -14
- package/dist/memory/compaction.js +31 -3
- package/dist/memory/prompts.js +5 -5
- package/dist/memory/tools/recall-observation.js +2 -2
- package/dist/pi/coding-agent/config.js +0 -11
- package/dist/pi/coding-agent/core/agent-session.js +3 -17
- package/dist/pi/coding-agent/core/extensions/loader.js +0 -6
- package/dist/pi/coding-agent/core/extensions/runner.js +7 -1
- package/dist/pi/coding-agent/core/keybindings.js +129 -2
- package/dist/pi/coding-agent/core/settings-manager.js +20 -0
- package/dist/pi/coding-agent/core/tools/bash.js +17 -63
- package/dist/pi/coding-agent/core/tools/edit.js +4 -141
- package/dist/pi/coding-agent/core/tools/find.js +0 -11
- package/dist/pi/coding-agent/core/tools/grep.js +0 -11
- package/dist/pi/coding-agent/core/tools/ls.js +0 -11
- package/dist/pi/coding-agent/core/tools/read.js +0 -12
- package/dist/pi/coding-agent/core/tools/render-utils.js +1 -14
- package/dist/pi/coding-agent/core/tools/write.js +2 -97
- package/dist/pi/coding-agent/modes/interactive/components/keybinding-hints.js +1 -1
- package/dist/pi/coding-agent/modes/interactive/components/visual-truncate.js +6 -12
- package/dist/pi/coding-agent/modes/interactive/theme/theme.js +1 -2
- package/dist/relay/models-fetch.js +13 -1
- package/dist/server/pi-bridge.js +57 -4
- package/dist/server/session-stream.js +7 -1
- package/package.json +1 -1
- package/dist/pi/coding-agent/core/export-html/ansi-to-html.js +0 -248
- package/dist/pi/coding-agent/core/export-html/index.js +0 -225
- package/dist/pi/coding-agent/core/export-html/tool-renderer.js +0 -107
- package/dist/pi/tui/autocomplete.js +0 -631
- package/dist/pi/tui/components/box.js +0 -103
- package/dist/pi/tui/components/cancellable-loader.js +0 -34
- package/dist/pi/tui/components/editor.js +0 -1915
- package/dist/pi/tui/components/image.js +0 -88
- package/dist/pi/tui/components/input.js +0 -425
- package/dist/pi/tui/components/loader.js +0 -68
- package/dist/pi/tui/components/markdown.js +0 -633
- package/dist/pi/tui/components/select-list.js +0 -158
- package/dist/pi/tui/components/settings-list.js +0 -184
- package/dist/pi/tui/components/spacer.js +0 -22
- package/dist/pi/tui/components/text.js +0 -88
- package/dist/pi/tui/components/truncated-text.js +0 -50
- package/dist/pi/tui/editor-component.js +0 -1
- package/dist/pi/tui/fuzzy.js +0 -109
- package/dist/pi/tui/index.js +0 -31
- package/dist/pi/tui/keybindings.js +0 -173
- package/dist/pi/tui/keys.js +0 -1172
- package/dist/pi/tui/kill-ring.js +0 -43
- package/dist/pi/tui/stdin-buffer.js +0 -360
- package/dist/pi/tui/terminal-image.js +0 -335
- package/dist/pi/tui/terminal.js +0 -324
- package/dist/pi/tui/tui.js +0 -1076
- package/dist/pi/tui/undo-stack.js +0 -24
- 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
|
|
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
|
|
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
|
-
|
|
130
|
+
parts.push(`\n${styledOutput}`);
|
|
141
131
|
}
|
|
142
132
|
else {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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,
|
|
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) {
|