Skip to content

Commit bf59808

Browse files
committed
🤖 feat: thread abort signals through file edit tools
Add abortSignal parameter to all file edit tools and helpers: - executeFileEditOperation() accepts and passes abortSignal - fileExists() accepts and passes abortSignal to runtime.stat() - file_edit_replace_string passes abortSignal from AI SDK - file_edit_replace_lines passes abortSignal from AI SDK - file_edit_insert passes abortSignal from AI SDK These tools now receive abort signals from AI SDK tool execution context and properly pass them to runtime operations (stat, readFile, writeFile). SSH runtime will cancel operations when abort is triggered. Local runtime ignores abort signals (fast operations).
1 parent e8f704e commit bf59808

File tree

5 files changed

+34
-15
lines changed

5 files changed

+34
-15
lines changed

src/services/tools/file_edit_insert.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@ export const createFileEditInsertTool: ToolFactory = (config: ToolConfiguration)
1818
return tool({
1919
description: TOOL_DEFINITIONS.file_edit_insert.description,
2020
inputSchema: TOOL_DEFINITIONS.file_edit_insert.schema,
21-
execute: async ({
22-
file_path,
23-
line_offset,
24-
content,
25-
create,
26-
}): Promise<FileEditInsertToolResult> => {
21+
execute: async (
22+
{
23+
file_path,
24+
line_offset,
25+
content,
26+
create,
27+
},
28+
{ abortSignal }
29+
): Promise<FileEditInsertToolResult> => {
2730
try {
2831
// Validate no redundant path prefix (must come first to catch absolute paths)
2932
const redundantPrefixValidation = validateNoRedundantPrefix(
@@ -57,7 +60,7 @@ export const createFileEditInsertTool: ToolFactory = (config: ToolConfiguration)
5760
const resolvedPath = config.runtime.normalizePath(file_path, config.cwd);
5861

5962
// Check if file exists using runtime
60-
const exists = await fileExists(config.runtime, resolvedPath);
63+
const exists = await fileExists(config.runtime, resolvedPath, abortSignal);
6164

6265
if (!exists) {
6366
if (!create) {
@@ -69,7 +72,7 @@ export const createFileEditInsertTool: ToolFactory = (config: ToolConfiguration)
6972

7073
// Create empty file using runtime helper
7174
try {
72-
await writeFileString(config.runtime, resolvedPath, "");
75+
await writeFileString(config.runtime, resolvedPath, "", abortSignal);
7376
} catch (err) {
7477
if (err instanceof RuntimeError) {
7578
return {
@@ -84,6 +87,7 @@ export const createFileEditInsertTool: ToolFactory = (config: ToolConfiguration)
8487
return executeFileEditOperation({
8588
config,
8689
filePath: file_path,
90+
abortSignal,
8791
operation: (originalContent) => {
8892
const lines = originalContent.split("\n");
8993

src/services/tools/file_edit_operation.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ interface ExecuteFileEditOperationOptions<TMetadata> {
2727
operation: (
2828
originalContent: string
2929
) => FileEditOperationResult<TMetadata> | Promise<FileEditOperationResult<TMetadata>>;
30+
abortSignal?: AbortSignal;
3031
}
3132

3233
/**
@@ -37,6 +38,7 @@ export async function executeFileEditOperation<TMetadata>({
3738
config,
3839
filePath,
3940
operation,
41+
abortSignal,
4042
}: ExecuteFileEditOperationOptions<TMetadata>): Promise<
4143
FileEditErrorResult | (FileEditDiffSuccessBase & TMetadata)
4244
> {
@@ -69,7 +71,7 @@ export async function executeFileEditOperation<TMetadata>({
6971
// Check if file exists and get stats using runtime
7072
let fileStat;
7173
try {
72-
fileStat = await config.runtime.stat(resolvedPath);
74+
fileStat = await config.runtime.stat(resolvedPath, abortSignal);
7375
} catch (err) {
7476
if (err instanceof RuntimeError) {
7577
return {
@@ -98,7 +100,7 @@ export async function executeFileEditOperation<TMetadata>({
98100
// Read file content using runtime helper
99101
let originalContent: string;
100102
try {
101-
originalContent = await readFileString(config.runtime, resolvedPath);
103+
originalContent = await readFileString(config.runtime, resolvedPath, abortSignal);
102104
} catch (err) {
103105
if (err instanceof RuntimeError) {
104106
return {
@@ -119,7 +121,7 @@ export async function executeFileEditOperation<TMetadata>({
119121

120122
// Write file using runtime helper
121123
try {
122-
await writeFileString(config.runtime, resolvedPath, operationResult.newContent);
124+
await writeFileString(config.runtime, resolvedPath, operationResult.newContent, abortSignal);
123125
} catch (err) {
124126
if (err instanceof RuntimeError) {
125127
return {

src/services/tools/file_edit_replace_lines.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,15 @@ export const createFileEditReplaceLinesTool: ToolFactory = (config: ToolConfigur
2626
return tool({
2727
description: TOOL_DEFINITIONS.file_edit_replace_lines.description,
2828
inputSchema: TOOL_DEFINITIONS.file_edit_replace_lines.schema,
29-
execute: async (args: LineReplaceArgs): Promise<FileEditReplaceLinesToolResult> => {
29+
execute: async (
30+
args: LineReplaceArgs,
31+
{ abortSignal }
32+
): Promise<FileEditReplaceLinesToolResult> => {
3033
const result = await executeFileEditOperation({
3134
config,
3235
filePath: args.file_path,
3336
operation: (originalContent) => handleLineReplace(args, originalContent),
37+
abortSignal,
3438
});
3539

3640
// handleLineReplace always returns lines_replaced and line_delta,

src/services/tools/file_edit_replace_string.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,15 @@ export const createFileEditReplaceStringTool: ToolFactory = (config: ToolConfigu
2626
return tool({
2727
description: TOOL_DEFINITIONS.file_edit_replace_string.description,
2828
inputSchema: TOOL_DEFINITIONS.file_edit_replace_string.schema,
29-
execute: async (args: StringReplaceArgs): Promise<FileEditReplaceStringToolResult> => {
29+
execute: async (
30+
args: StringReplaceArgs,
31+
{ abortSignal }
32+
): Promise<FileEditReplaceStringToolResult> => {
3033
return executeFileEditOperation({
3134
config,
3235
filePath: args.file_path,
3336
operation: (originalContent) => handleStringReplace(args, originalContent),
37+
abortSignal,
3438
});
3539
},
3640
});

src/utils/runtime/fileExists.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@ import type { Runtime } from "@/runtime/Runtime";
44
* Check if a path exists using runtime.stat()
55
* @param runtime Runtime instance to use
66
* @param path Path to check
7+
* @param abortSignal Optional abort signal to cancel the operation
78
* @returns True if path exists, false otherwise
89
*/
9-
export async function fileExists(runtime: Runtime, path: string): Promise<boolean> {
10+
export async function fileExists(
11+
runtime: Runtime,
12+
path: string,
13+
abortSignal?: AbortSignal
14+
): Promise<boolean> {
1015
try {
11-
await runtime.stat(path);
16+
await runtime.stat(path, abortSignal);
1217
return true;
1318
} catch {
1419
return false;

0 commit comments

Comments
 (0)