@oh-my-pi/pi-coding-agent 14.0.5 → 14.1.1
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/CHANGELOG.md +120 -0
- package/package.json +8 -8
- package/src/async/index.ts +1 -0
- package/src/async/job-manager.ts +43 -10
- package/src/async/support.ts +5 -0
- package/src/cli/list-models.ts +96 -57
- package/src/commit/agentic/tools/analyze-file.ts +1 -2
- package/src/commit/model-selection.ts +16 -13
- package/src/config/mcp-schema.json +1 -1
- package/src/config/model-equivalence.ts +675 -0
- package/src/config/model-registry.ts +242 -45
- package/src/config/model-resolver.ts +282 -65
- package/src/config/settings-schema.ts +27 -3
- package/src/config/settings.ts +1 -1
- package/src/cursor.ts +64 -23
- package/src/edit/index.ts +254 -89
- package/src/edit/modes/chunk.ts +336 -57
- package/src/edit/modes/hashline.ts +51 -26
- package/src/edit/modes/patch.ts +16 -10
- package/src/edit/modes/replace.ts +15 -7
- package/src/edit/renderer.ts +248 -94
- package/src/export/html/template.css +82 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +614 -97
- package/src/extensibility/custom-tools/types.ts +0 -3
- package/src/extensibility/extensions/loader.ts +16 -0
- package/src/extensibility/extensions/runner.ts +2 -7
- package/src/extensibility/extensions/types.ts +8 -4
- package/src/internal-urls/docs-index.generated.ts +4 -4
- package/src/internal-urls/jobs-protocol.ts +2 -1
- package/src/ipy/executor.ts +447 -52
- package/src/ipy/kernel.ts +39 -13
- package/src/lsp/client.ts +55 -1
- package/src/lsp/index.ts +8 -0
- package/src/lsp/types.ts +6 -0
- package/src/main.ts +6 -2
- package/src/memories/index.ts +7 -6
- package/src/modes/acp/acp-agent.ts +4 -1
- package/src/modes/components/bash-execution.ts +16 -4
- package/src/modes/components/model-selector.ts +221 -64
- package/src/modes/components/status-line/presets.ts +17 -6
- package/src/modes/components/status-line/segments.ts +15 -0
- package/src/modes/components/status-line-segment-editor.ts +1 -0
- package/src/modes/components/status-line.ts +7 -1
- package/src/modes/components/tool-execution.ts +145 -75
- package/src/modes/controllers/command-controller.ts +42 -1
- package/src/modes/controllers/event-controller.ts +4 -1
- package/src/modes/controllers/extension-ui-controller.ts +28 -5
- package/src/modes/controllers/input-controller.ts +9 -3
- package/src/modes/controllers/selector-controller.ts +17 -6
- package/src/modes/interactive-mode.ts +19 -3
- package/src/modes/print-mode.ts +13 -4
- package/src/modes/prompt-action-autocomplete.ts +3 -5
- package/src/modes/rpc/rpc-mode.ts +8 -2
- package/src/modes/shared.ts +2 -2
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/system/system-prompt.md +5 -1
- package/src/prompts/tools/bash.md +16 -1
- package/src/prompts/tools/cancel-job.md +1 -1
- package/src/prompts/tools/chunk-edit.md +191 -163
- package/src/prompts/tools/hashline.md +11 -11
- package/src/prompts/tools/patch.md +10 -5
- package/src/prompts/tools/{await.md → poll.md} +1 -1
- package/src/prompts/tools/read-chunk.md +12 -3
- package/src/prompts/tools/read.md +9 -0
- package/src/prompts/tools/task.md +2 -2
- package/src/prompts/tools/vim.md +98 -0
- package/src/prompts/tools/write.md +1 -0
- package/src/sdk.ts +758 -725
- package/src/session/agent-session.ts +187 -40
- package/src/session/session-manager.ts +50 -4
- package/src/slash-commands/builtin-registry.ts +17 -0
- package/src/task/executor.ts +9 -5
- package/src/task/index.ts +3 -5
- package/src/task/types.ts +2 -2
- package/src/tools/bash.ts +240 -57
- package/src/tools/cancel-job.ts +2 -1
- package/src/tools/find.ts +5 -2
- package/src/tools/grep.ts +77 -8
- package/src/tools/index.ts +48 -19
- package/src/tools/inspect-image.ts +1 -1
- package/src/tools/{await-tool.ts → poll-tool.ts} +38 -31
- package/src/tools/python.ts +293 -278
- package/src/tools/read.ts +218 -1
- package/src/tools/sqlite-reader.ts +623 -0
- package/src/tools/submit-result.ts +5 -2
- package/src/tools/todo-write.ts +8 -2
- package/src/tools/vim.ts +966 -0
- package/src/tools/write.ts +187 -1
- package/src/utils/commit-message-generator.ts +1 -0
- package/src/utils/edit-mode.ts +2 -1
- package/src/utils/git.ts +24 -1
- package/src/utils/session-color.ts +55 -0
- package/src/utils/title-generator.ts +16 -7
- package/src/vim/buffer.ts +309 -0
- package/src/vim/commands.ts +382 -0
- package/src/vim/engine.ts +2426 -0
- package/src/vim/parser.ts +151 -0
- package/src/vim/render.ts +252 -0
- package/src/vim/types.ts +197 -0
package/src/cursor.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
CursorExecHandlers as ICursorExecHandlers,
|
|
14
14
|
ToolResultMessage,
|
|
15
15
|
} from "@oh-my-pi/pi-ai";
|
|
16
|
+
import { sanitizeText } from "@oh-my-pi/pi-natives";
|
|
16
17
|
import { resolveToCwd } from "./tools/path-utils";
|
|
17
18
|
|
|
18
19
|
interface CursorExecBridgeOptions {
|
|
@@ -65,12 +66,16 @@ async function executeTool(
|
|
|
65
66
|
|
|
66
67
|
const onUpdate: AgentToolUpdateCallback<unknown> | undefined = options.emitEvent
|
|
67
68
|
? partialResult => {
|
|
69
|
+
const sanitizedResult: AgentToolResult<unknown> = {
|
|
70
|
+
content: partialResult.content.map(c => (c.type === "text" ? { ...c, text: sanitizeText(c.text) } : c)),
|
|
71
|
+
details: partialResult.details,
|
|
72
|
+
};
|
|
68
73
|
options.emitEvent?.({
|
|
69
74
|
type: "tool_execution_update",
|
|
70
75
|
toolCallId,
|
|
71
76
|
toolName,
|
|
72
77
|
args,
|
|
73
|
-
partialResult,
|
|
78
|
+
partialResult: sanitizedResult,
|
|
74
79
|
});
|
|
75
80
|
}
|
|
76
81
|
: undefined;
|
|
@@ -89,7 +94,11 @@ async function executeTool(
|
|
|
89
94
|
isError = true;
|
|
90
95
|
}
|
|
91
96
|
|
|
92
|
-
|
|
97
|
+
const sanitizedFinalResult: AgentToolResult<unknown> = {
|
|
98
|
+
content: result.content.map(c => (c.type === "text" ? { ...c, text: sanitizeText(c.text) } : c)),
|
|
99
|
+
details: result.details,
|
|
100
|
+
};
|
|
101
|
+
options.emitEvent?.({ type: "tool_execution_end", toolCallId, toolName, result: sanitizedFinalResult, isError });
|
|
93
102
|
|
|
94
103
|
return createToolResultMessage(toolCallId, toolName, result, isError);
|
|
95
104
|
}
|
|
@@ -233,21 +242,43 @@ export class CursorExecHandlers implements ICursorExecHandlers {
|
|
|
233
242
|
let result: AgentToolResult<unknown>;
|
|
234
243
|
let isError = false;
|
|
235
244
|
|
|
236
|
-
|
|
237
|
-
let
|
|
245
|
+
let rawText = "";
|
|
246
|
+
let sanitizedRawText = "";
|
|
247
|
+
let streamedSanitizedText = "";
|
|
248
|
+
let canStreamSanitizedDelta = true;
|
|
238
249
|
const onUpdate: AgentToolUpdateCallback<unknown> = partialResult => {
|
|
250
|
+
const newRawText = partialResult.content.map(c => (c.type === "text" ? c.text : "")).join("");
|
|
251
|
+
if (newRawText === rawText) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
rawText = newRawText;
|
|
255
|
+
sanitizedRawText = sanitizeText(newRawText);
|
|
256
|
+
const sanitizedPartialResult: AgentToolResult<unknown> = {
|
|
257
|
+
content: [{ type: "text" as const, text: sanitizedRawText }],
|
|
258
|
+
details: partialResult.details,
|
|
259
|
+
};
|
|
239
260
|
this.options.emitEvent?.({
|
|
240
261
|
type: "tool_execution_update",
|
|
241
262
|
toolCallId,
|
|
242
263
|
toolName,
|
|
243
264
|
args: toolArgs,
|
|
244
|
-
partialResult,
|
|
265
|
+
partialResult: sanitizedPartialResult,
|
|
245
266
|
});
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
267
|
+
if (!canStreamSanitizedDelta) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (sanitizedRawText.startsWith(streamedSanitizedText)) {
|
|
271
|
+
const sanitizedDelta = sanitizedRawText.slice(streamedSanitizedText.length);
|
|
272
|
+
streamedSanitizedText = sanitizedRawText;
|
|
273
|
+
if (sanitizedDelta) {
|
|
274
|
+
callbacks.onStdout(sanitizedDelta);
|
|
275
|
+
}
|
|
276
|
+
return;
|
|
250
277
|
}
|
|
278
|
+
// Cursor's shell-stream callback is append-only. Once the sanitized snapshot
|
|
279
|
+
// stops being a prefix extension, we can no longer repair the stream safely.
|
|
280
|
+
// Keep emitting full snapshots via tool_execution_update, but stop stdout deltas.
|
|
281
|
+
canStreamSanitizedDelta = false;
|
|
251
282
|
};
|
|
252
283
|
|
|
253
284
|
try {
|
|
@@ -260,12 +291,30 @@ export class CursorExecHandlers implements ICursorExecHandlers {
|
|
|
260
291
|
|
|
261
292
|
// onUpdate may not fire for every chunk — flush any remaining output
|
|
262
293
|
// from the final result that wasn't already streamed.
|
|
263
|
-
const
|
|
264
|
-
if (
|
|
265
|
-
|
|
294
|
+
const finalRawText = result.content.map(c => (c.type === "text" ? c.text : "")).join("");
|
|
295
|
+
if (finalRawText !== rawText) {
|
|
296
|
+
rawText = finalRawText;
|
|
297
|
+
sanitizedRawText = sanitizeText(finalRawText);
|
|
298
|
+
}
|
|
299
|
+
if (canStreamSanitizedDelta && sanitizedRawText.startsWith(streamedSanitizedText)) {
|
|
300
|
+
const finalDelta = sanitizedRawText.slice(streamedSanitizedText.length);
|
|
301
|
+
streamedSanitizedText = sanitizedRawText;
|
|
302
|
+
if (finalDelta) {
|
|
303
|
+
callbacks.onStdout(finalDelta);
|
|
304
|
+
}
|
|
266
305
|
}
|
|
267
306
|
|
|
268
|
-
|
|
307
|
+
const sanitizedFinalResult: AgentToolResult<unknown> = {
|
|
308
|
+
content: result.content.map(c => (c.type === "text" ? { ...c, text: sanitizeText(c.text) } : c)),
|
|
309
|
+
details: result.details,
|
|
310
|
+
};
|
|
311
|
+
this.options.emitEvent?.({
|
|
312
|
+
type: "tool_execution_end",
|
|
313
|
+
toolCallId,
|
|
314
|
+
toolName,
|
|
315
|
+
result: sanitizedFinalResult,
|
|
316
|
+
isError,
|
|
317
|
+
});
|
|
269
318
|
return createToolResultMessage(toolCallId, toolName, result, isError);
|
|
270
319
|
}
|
|
271
320
|
|
|
@@ -285,16 +334,8 @@ export class CursorExecHandlers implements ICursorExecHandlers {
|
|
|
285
334
|
if (!tool) {
|
|
286
335
|
const availableTools = Array.from(this.options.tools.keys()).filter(name => name.startsWith("mcp_"));
|
|
287
336
|
const message = formatMcpToolErrorMessage(toolName, availableTools);
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
toolCallId,
|
|
291
|
-
toolName,
|
|
292
|
-
content: [{ type: "text", text: message }],
|
|
293
|
-
details: {},
|
|
294
|
-
isError: true,
|
|
295
|
-
timestamp: Date.now(),
|
|
296
|
-
};
|
|
297
|
-
return toolResult;
|
|
337
|
+
const result = buildToolErrorResult(message);
|
|
338
|
+
return createToolResultMessage(toolCallId, toolName, result, true);
|
|
298
339
|
}
|
|
299
340
|
|
|
300
341
|
const args = Object.keys(call.args ?? {}).length > 0 ? call.args : decodeMcpArgs(call.rawArgs ?? {});
|
package/src/edit/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
3
|
+
import type { Static } from "@sinclair/typebox";
|
|
3
4
|
import {
|
|
4
5
|
createLspWritethrough,
|
|
5
6
|
type FileDiagnosticsResult,
|
|
@@ -12,19 +13,41 @@ import hashlineDescription from "../prompts/tools/hashline.md" with { type: "tex
|
|
|
12
13
|
import patchDescription from "../prompts/tools/patch.md" with { type: "text" };
|
|
13
14
|
import replaceDescription from "../prompts/tools/replace.md" with { type: "text" };
|
|
14
15
|
import type { ToolSession } from "../tools";
|
|
16
|
+
import { VimTool, vimSchema } from "../tools/vim";
|
|
15
17
|
import { type EditMode, normalizeEditMode, resolveEditMode } from "../utils/edit-mode";
|
|
18
|
+
import type { VimToolDetails } from "../vim/types";
|
|
16
19
|
import {
|
|
17
20
|
type ChunkParams,
|
|
21
|
+
type ChunkToolEdit,
|
|
18
22
|
chunkEditParamsSchema,
|
|
19
|
-
|
|
23
|
+
executeChunkSingle,
|
|
20
24
|
isChunkParams,
|
|
25
|
+
parseChunkEditPath,
|
|
21
26
|
resolveAnchorStyle,
|
|
22
27
|
resolveChunkAutoIndent,
|
|
23
28
|
} from "./modes/chunk";
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
import {
|
|
30
|
+
executeHashlineSingle,
|
|
31
|
+
type HashlineParams,
|
|
32
|
+
type HashlineToolEdit,
|
|
33
|
+
hashlineEditParamsSchema,
|
|
34
|
+
isHashlineParams,
|
|
35
|
+
} from "./modes/hashline";
|
|
36
|
+
import {
|
|
37
|
+
executePatchSingle,
|
|
38
|
+
isPatchParams,
|
|
39
|
+
type PatchEditEntry,
|
|
40
|
+
type PatchParams,
|
|
41
|
+
patchEditSchema,
|
|
42
|
+
} from "./modes/patch";
|
|
43
|
+
import {
|
|
44
|
+
executeReplaceSingle,
|
|
45
|
+
isReplaceParams,
|
|
46
|
+
type ReplaceEditEntry,
|
|
47
|
+
type ReplaceParams,
|
|
48
|
+
replaceEditSchema,
|
|
49
|
+
} from "./modes/replace";
|
|
50
|
+
import { type EditToolDetails, type EditToolPerFileResult, getLspBatchRequest, type LspBatchRequest } from "./renderer";
|
|
28
51
|
|
|
29
52
|
export { DEFAULT_EDIT_MODE, type EditMode, normalizeEditMode } from "../utils/edit-mode";
|
|
30
53
|
export * from "./diff";
|
|
@@ -40,22 +63,25 @@ type TInput =
|
|
|
40
63
|
| typeof replaceEditSchema
|
|
41
64
|
| typeof patchEditSchema
|
|
42
65
|
| typeof hashlineEditParamsSchema
|
|
43
|
-
| typeof chunkEditParamsSchema
|
|
66
|
+
| typeof chunkEditParamsSchema
|
|
67
|
+
| typeof vimSchema;
|
|
44
68
|
|
|
45
|
-
type
|
|
46
|
-
|
|
47
|
-
type
|
|
48
|
-
params: EditParams;
|
|
49
|
-
signal: AbortSignal | undefined;
|
|
50
|
-
batchRequest: LspBatchRequest | undefined;
|
|
51
|
-
};
|
|
69
|
+
type VimParams = Static<typeof vimSchema>;
|
|
70
|
+
type EditParams = ReplaceParams | PatchParams | HashlineParams | ChunkParams | VimParams;
|
|
71
|
+
type EditToolResultDetails = EditToolDetails | VimToolDetails;
|
|
52
72
|
|
|
53
73
|
type EditModeDefinition = {
|
|
54
74
|
description: (session: ToolSession) => string;
|
|
55
75
|
parameters: TInput;
|
|
56
76
|
invalidParamsMessage: string;
|
|
57
77
|
validate: (params: EditParams) => boolean;
|
|
58
|
-
execute: (
|
|
78
|
+
execute: (
|
|
79
|
+
tool: EditTool,
|
|
80
|
+
params: EditParams,
|
|
81
|
+
signal: AbortSignal | undefined,
|
|
82
|
+
batchRequest: LspBatchRequest | undefined,
|
|
83
|
+
onUpdate?: (partialResult: AgentToolResult<EditToolResultDetails, TInput>) => void,
|
|
84
|
+
) => Promise<AgentToolResult<EditToolResultDetails, TInput>>;
|
|
59
85
|
};
|
|
60
86
|
|
|
61
87
|
function resolveConfiguredEditMode(rawEditMode: string): EditMode | undefined {
|
|
@@ -71,6 +97,10 @@ function resolveConfiguredEditMode(rawEditMode: string): EditMode | undefined {
|
|
|
71
97
|
return editMode;
|
|
72
98
|
}
|
|
73
99
|
|
|
100
|
+
function isVimParams(params: EditParams): params is VimParams {
|
|
101
|
+
return typeof params === "object" && params !== null && "file" in params && typeof params.file === "string";
|
|
102
|
+
}
|
|
103
|
+
|
|
74
104
|
function resolveAllowFuzzy(session: ToolSession, rawValue: string): boolean {
|
|
75
105
|
switch (rawValue) {
|
|
76
106
|
case "true":
|
|
@@ -106,6 +136,94 @@ function createEditWritethrough(session: ToolSession): WritethroughCallback {
|
|
|
106
136
|
return enableLsp ? createLspWritethrough(session.cwd, { enableFormat, enableDiagnostics }) : writethroughNoop;
|
|
107
137
|
}
|
|
108
138
|
|
|
139
|
+
/** Group items by a key, preserving insertion order. */
|
|
140
|
+
function groupBy<T, K>(items: T[], key: (item: T) => K): Map<K, T[]> {
|
|
141
|
+
const map = new Map<K, T[]>();
|
|
142
|
+
for (const item of items) {
|
|
143
|
+
const k = key(item);
|
|
144
|
+
let arr = map.get(k);
|
|
145
|
+
if (!arr) {
|
|
146
|
+
arr = [];
|
|
147
|
+
map.set(k, arr);
|
|
148
|
+
}
|
|
149
|
+
arr.push(item);
|
|
150
|
+
}
|
|
151
|
+
return map;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Run single-file executors for each file group and aggregate results. */
|
|
155
|
+
async function executePerFile(
|
|
156
|
+
fileEntries: {
|
|
157
|
+
path: string;
|
|
158
|
+
run: (batchRequest: LspBatchRequest | undefined) => Promise<AgentToolResult<EditToolDetails, any>>;
|
|
159
|
+
}[],
|
|
160
|
+
outerBatchRequest: LspBatchRequest | undefined,
|
|
161
|
+
onUpdate?: (partialResult: AgentToolResult<EditToolDetails, TInput>) => void,
|
|
162
|
+
): Promise<AgentToolResult<EditToolDetails, TInput>> {
|
|
163
|
+
if (fileEntries.length === 1) {
|
|
164
|
+
// Single file — just run directly, no wrapping
|
|
165
|
+
return fileEntries[0].run(outerBatchRequest);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const perFileResults: EditToolPerFileResult[] = [];
|
|
169
|
+
const contentTexts: string[] = [];
|
|
170
|
+
|
|
171
|
+
for (let i = 0; i < fileEntries.length; i++) {
|
|
172
|
+
const { path, run } = fileEntries[i];
|
|
173
|
+
const isLast = i === fileEntries.length - 1;
|
|
174
|
+
const batchRequest: LspBatchRequest | undefined = outerBatchRequest
|
|
175
|
+
? { id: outerBatchRequest.id, flush: isLast && outerBatchRequest.flush }
|
|
176
|
+
: undefined;
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const result = await run(batchRequest);
|
|
180
|
+
const details = result.details;
|
|
181
|
+
perFileResults.push({
|
|
182
|
+
path,
|
|
183
|
+
diff: details?.diff ?? "",
|
|
184
|
+
firstChangedLine: details?.firstChangedLine,
|
|
185
|
+
diagnostics: details?.diagnostics,
|
|
186
|
+
op: details?.op,
|
|
187
|
+
move: details?.move,
|
|
188
|
+
meta: details?.meta,
|
|
189
|
+
});
|
|
190
|
+
const text = result.content?.find(c => c.type === "text")?.text ?? "";
|
|
191
|
+
if (text) contentTexts.push(text);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
const errorText = err instanceof Error ? err.message : String(err);
|
|
194
|
+
perFileResults.push({ path, diff: "", isError: true, errorText });
|
|
195
|
+
contentTexts.push(`Error editing ${path}: ${errorText}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Emit partial result after each file so UI shows progressive completion
|
|
199
|
+
if (!isLast && onUpdate) {
|
|
200
|
+
onUpdate({
|
|
201
|
+
content: [{ type: "text", text: contentTexts.join("\n") }],
|
|
202
|
+
details: {
|
|
203
|
+
diff: perFileResults
|
|
204
|
+
.map(r => r.diff)
|
|
205
|
+
.filter(Boolean)
|
|
206
|
+
.join("\n"),
|
|
207
|
+
firstChangedLine: perFileResults.find(r => r.firstChangedLine)?.firstChangedLine,
|
|
208
|
+
perFileResults: [...perFileResults],
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
content: [{ type: "text", text: contentTexts.join("\n") }],
|
|
216
|
+
details: {
|
|
217
|
+
diff: perFileResults
|
|
218
|
+
.map(r => r.diff)
|
|
219
|
+
.filter(Boolean)
|
|
220
|
+
.join("\n"),
|
|
221
|
+
firstChangedLine: perFileResults.find(r => r.firstChangedLine)?.firstChangedLine,
|
|
222
|
+
perFileResults,
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
109
227
|
export class EditTool implements AgentTool<TInput> {
|
|
110
228
|
readonly name = "edit";
|
|
111
229
|
readonly label = "Edit";
|
|
@@ -117,6 +235,7 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
117
235
|
readonly #fuzzyThreshold: number;
|
|
118
236
|
readonly #writethrough: WritethroughCallback;
|
|
119
237
|
readonly #editMode?: EditMode;
|
|
238
|
+
readonly #vimTool: VimTool;
|
|
120
239
|
readonly #pendingDeferredFetches = new Map<string, AbortController>();
|
|
121
240
|
|
|
122
241
|
constructor(private readonly session: ToolSession) {
|
|
@@ -130,6 +249,7 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
130
249
|
this.#allowFuzzy = resolveAllowFuzzy(session, editFuzzy);
|
|
131
250
|
this.#fuzzyThreshold = resolveFuzzyThreshold(session, editFuzzyThreshold);
|
|
132
251
|
this.#writethrough = createEditWritethrough(session);
|
|
252
|
+
this.#vimTool = new VimTool(session);
|
|
133
253
|
}
|
|
134
254
|
|
|
135
255
|
get mode(): EditMode {
|
|
@@ -145,51 +265,19 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
145
265
|
return this.#getModeDefinition().parameters;
|
|
146
266
|
}
|
|
147
267
|
|
|
148
|
-
async execute(
|
|
149
|
-
_toolCallId: string,
|
|
150
|
-
params: ReplaceParams,
|
|
151
|
-
signal?: AbortSignal,
|
|
152
|
-
_onUpdate?: AgentToolUpdateCallback<EditToolDetails, TInput>,
|
|
153
|
-
context?: AgentToolContext,
|
|
154
|
-
): Promise<AgentToolResult<EditToolDetails, TInput>>;
|
|
155
|
-
async execute(
|
|
156
|
-
_toolCallId: string,
|
|
157
|
-
params: PatchParams,
|
|
158
|
-
signal?: AbortSignal,
|
|
159
|
-
_onUpdate?: AgentToolUpdateCallback<EditToolDetails, TInput>,
|
|
160
|
-
context?: AgentToolContext,
|
|
161
|
-
): Promise<AgentToolResult<EditToolDetails, TInput>>;
|
|
162
|
-
async execute(
|
|
163
|
-
_toolCallId: string,
|
|
164
|
-
params: HashlineParams,
|
|
165
|
-
signal?: AbortSignal,
|
|
166
|
-
_onUpdate?: AgentToolUpdateCallback<EditToolDetails, TInput>,
|
|
167
|
-
context?: AgentToolContext,
|
|
168
|
-
): Promise<AgentToolResult<EditToolDetails, TInput>>;
|
|
169
|
-
async execute(
|
|
170
|
-
_toolCallId: string,
|
|
171
|
-
params: ChunkParams,
|
|
172
|
-
signal?: AbortSignal,
|
|
173
|
-
_onUpdate?: AgentToolUpdateCallback<EditToolDetails, TInput>,
|
|
174
|
-
context?: AgentToolContext,
|
|
175
|
-
): Promise<AgentToolResult<EditToolDetails, TInput>>;
|
|
176
268
|
async execute(
|
|
177
269
|
_toolCallId: string,
|
|
178
270
|
params: EditParams,
|
|
179
271
|
signal?: AbortSignal,
|
|
180
|
-
|
|
272
|
+
onUpdate?: AgentToolUpdateCallback<EditToolResultDetails, TInput>,
|
|
181
273
|
context?: AgentToolContext,
|
|
182
|
-
): Promise<AgentToolResult<
|
|
274
|
+
): Promise<AgentToolResult<EditToolResultDetails, TInput>> {
|
|
183
275
|
const modeDefinition = this.#getModeDefinition();
|
|
184
276
|
if (!modeDefinition.validate(params)) {
|
|
185
277
|
throw new Error(modeDefinition.invalidParamsMessage);
|
|
186
278
|
}
|
|
187
279
|
|
|
188
|
-
return modeDefinition.execute(this,
|
|
189
|
-
params,
|
|
190
|
-
signal,
|
|
191
|
-
batchRequest: getLspBatchRequest(context?.toolCall),
|
|
192
|
-
});
|
|
280
|
+
return modeDefinition.execute(this, params, signal, getLspBatchRequest(context?.toolCall), onUpdate);
|
|
193
281
|
}
|
|
194
282
|
|
|
195
283
|
#getModeDefinition(): EditModeDefinition {
|
|
@@ -203,15 +291,29 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
203
291
|
parameters: chunkEditParamsSchema,
|
|
204
292
|
invalidParamsMessage: "Invalid edit parameters for chunk mode.",
|
|
205
293
|
validate: isChunkParams,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
294
|
+
execute: (
|
|
295
|
+
tool: EditTool,
|
|
296
|
+
params: EditParams,
|
|
297
|
+
signal: AbortSignal | undefined,
|
|
298
|
+
batchRequest: LspBatchRequest | undefined,
|
|
299
|
+
onUpdate?: (partialResult: AgentToolResult<EditToolDetails, TInput>) => void,
|
|
300
|
+
) => {
|
|
301
|
+
const { edits } = params as ChunkParams;
|
|
302
|
+
const byFile = groupBy(edits, (e: ChunkToolEdit) => parseChunkEditPath(e.path).filePath);
|
|
303
|
+
const entries = [...byFile.entries()].map(([filePath, fileEdits]) => ({
|
|
304
|
+
path: filePath,
|
|
305
|
+
run: (br: LspBatchRequest | undefined) =>
|
|
306
|
+
executeChunkSingle({
|
|
307
|
+
session: tool.session,
|
|
308
|
+
path: filePath,
|
|
309
|
+
edits: fileEdits,
|
|
310
|
+
signal,
|
|
311
|
+
batchRequest: br,
|
|
312
|
+
writethrough: tool.#writethrough,
|
|
313
|
+
beginDeferredDiagnosticsForPath: p => tool.#beginDeferredDiagnosticsForPath(p),
|
|
314
|
+
}),
|
|
315
|
+
}));
|
|
316
|
+
return executePerFile(entries, batchRequest, onUpdate);
|
|
215
317
|
},
|
|
216
318
|
},
|
|
217
319
|
patch: {
|
|
@@ -219,17 +321,29 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
219
321
|
parameters: patchEditSchema,
|
|
220
322
|
invalidParamsMessage: "Invalid edit parameters for patch mode.",
|
|
221
323
|
validate: isPatchParams,
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
324
|
+
execute: (
|
|
325
|
+
tool: EditTool,
|
|
326
|
+
params: EditParams,
|
|
327
|
+
signal: AbortSignal | undefined,
|
|
328
|
+
batchRequest: LspBatchRequest | undefined,
|
|
329
|
+
onUpdate?: (partialResult: AgentToolResult<EditToolDetails, TInput>) => void,
|
|
330
|
+
) => {
|
|
331
|
+
const { edits } = params as PatchParams;
|
|
332
|
+
const entries = edits.map((entry: PatchEditEntry) => ({
|
|
333
|
+
path: entry.path,
|
|
334
|
+
run: (br: LspBatchRequest | undefined) =>
|
|
335
|
+
executePatchSingle({
|
|
336
|
+
session: tool.session,
|
|
337
|
+
params: entry,
|
|
338
|
+
signal,
|
|
339
|
+
batchRequest: br,
|
|
340
|
+
allowFuzzy: tool.#allowFuzzy,
|
|
341
|
+
fuzzyThreshold: tool.#fuzzyThreshold,
|
|
342
|
+
writethrough: tool.#writethrough,
|
|
343
|
+
beginDeferredDiagnosticsForPath: p => tool.#beginDeferredDiagnosticsForPath(p),
|
|
344
|
+
}),
|
|
345
|
+
}));
|
|
346
|
+
return executePerFile(entries, batchRequest, onUpdate);
|
|
233
347
|
},
|
|
234
348
|
},
|
|
235
349
|
hashline: {
|
|
@@ -237,15 +351,29 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
237
351
|
parameters: hashlineEditParamsSchema,
|
|
238
352
|
invalidParamsMessage: "Invalid edit parameters for hashline mode.",
|
|
239
353
|
validate: isHashlineParams,
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
354
|
+
execute: (
|
|
355
|
+
tool: EditTool,
|
|
356
|
+
params: EditParams,
|
|
357
|
+
signal: AbortSignal | undefined,
|
|
358
|
+
batchRequest: LspBatchRequest | undefined,
|
|
359
|
+
onUpdate?: (partialResult: AgentToolResult<EditToolDetails, TInput>) => void,
|
|
360
|
+
) => {
|
|
361
|
+
const { edits } = params as HashlineParams;
|
|
362
|
+
const byFile = groupBy(edits, (e: HashlineToolEdit) => e.path);
|
|
363
|
+
const entries = [...byFile.entries()].map(([path, fileEdits]) => ({
|
|
364
|
+
path,
|
|
365
|
+
run: (br: LspBatchRequest | undefined) =>
|
|
366
|
+
executeHashlineSingle({
|
|
367
|
+
session: tool.session,
|
|
368
|
+
path,
|
|
369
|
+
edits: fileEdits,
|
|
370
|
+
signal,
|
|
371
|
+
batchRequest: br,
|
|
372
|
+
writethrough: tool.#writethrough,
|
|
373
|
+
beginDeferredDiagnosticsForPath: p => tool.#beginDeferredDiagnosticsForPath(p),
|
|
374
|
+
}),
|
|
375
|
+
}));
|
|
376
|
+
return executePerFile(entries, batchRequest, onUpdate);
|
|
249
377
|
},
|
|
250
378
|
},
|
|
251
379
|
replace: {
|
|
@@ -253,17 +381,54 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
253
381
|
parameters: replaceEditSchema,
|
|
254
382
|
invalidParamsMessage: "Invalid edit parameters for replace mode.",
|
|
255
383
|
validate: isReplaceParams,
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
384
|
+
execute: (
|
|
385
|
+
tool: EditTool,
|
|
386
|
+
params: EditParams,
|
|
387
|
+
signal: AbortSignal | undefined,
|
|
388
|
+
batchRequest: LspBatchRequest | undefined,
|
|
389
|
+
onUpdate?: (partialResult: AgentToolResult<EditToolDetails, TInput>) => void,
|
|
390
|
+
) => {
|
|
391
|
+
const { edits } = params as ReplaceParams;
|
|
392
|
+
const entries = edits.map((entry: ReplaceEditEntry) => ({
|
|
393
|
+
path: entry.path,
|
|
394
|
+
run: (br: LspBatchRequest | undefined) =>
|
|
395
|
+
executeReplaceSingle({
|
|
396
|
+
session: tool.session,
|
|
397
|
+
params: entry,
|
|
398
|
+
signal,
|
|
399
|
+
batchRequest: br,
|
|
400
|
+
allowFuzzy: tool.#allowFuzzy,
|
|
401
|
+
fuzzyThreshold: tool.#fuzzyThreshold,
|
|
402
|
+
writethrough: tool.#writethrough,
|
|
403
|
+
beginDeferredDiagnosticsForPath: p => tool.#beginDeferredDiagnosticsForPath(p),
|
|
404
|
+
}),
|
|
405
|
+
}));
|
|
406
|
+
return executePerFile(entries, batchRequest, onUpdate);
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
vim: {
|
|
410
|
+
description: () => this.#vimTool.description,
|
|
411
|
+
parameters: vimSchema,
|
|
412
|
+
invalidParamsMessage: "Invalid edit parameters for vim mode.",
|
|
413
|
+
validate: isVimParams,
|
|
414
|
+
execute: async (
|
|
415
|
+
tool: EditTool,
|
|
416
|
+
params: EditParams,
|
|
417
|
+
signal: AbortSignal | undefined,
|
|
418
|
+
_batchRequest: LspBatchRequest | undefined,
|
|
419
|
+
onUpdate?: (partialResult: AgentToolResult<EditToolResultDetails, TInput>) => void,
|
|
420
|
+
) => {
|
|
421
|
+
const handleUpdate = onUpdate
|
|
422
|
+
? (partialResult: AgentToolResult<VimToolDetails>) => {
|
|
423
|
+
onUpdate(partialResult as AgentToolResult<EditToolResultDetails, TInput>);
|
|
424
|
+
}
|
|
425
|
+
: undefined;
|
|
426
|
+
return (await tool.#vimTool.execute(
|
|
427
|
+
"edit",
|
|
428
|
+
params as VimParams,
|
|
429
|
+
signal,
|
|
430
|
+
handleUpdate,
|
|
431
|
+
)) as AgentToolResult<EditToolResultDetails, TInput>;
|
|
267
432
|
},
|
|
268
433
|
},
|
|
269
434
|
}[this.mode];
|