feat(tools): propagate timeout abort signals to tool execution

This commit is contained in:
William Valentin
2026-02-15 22:05:43 -08:00
parent 0220ec10dd
commit 2cdfb66071
10 changed files with 113 additions and 18 deletions
+17 -3
View File
@@ -1,5 +1,5 @@
import { execFile } from 'child_process';
import type { Tool, ToolResult } from '../types.js';
import type { Tool, ToolResult, ToolExecutionContext } from '../types.js';
interface ShellExecArgs {
command: string;
@@ -19,16 +19,19 @@ export const shellExecTool: Tool = {
},
required: ['command'],
},
execute: async (rawArgs: unknown): Promise<ToolResult> => {
execute: async (rawArgs: unknown, context?: ToolExecutionContext): Promise<ToolResult> => {
const args = rawArgs as ShellExecArgs;
const timeout = args.timeout ?? 30_000;
return new Promise((resolve) => {
execFile('bash', ['-c', args.command], {
const child = execFile('bash', ['-c', args.command], {
cwd: args.cwd,
timeout,
maxBuffer: 1024 * 1024,
}, (error, stdout, stderr) => {
if (context?.signal) {
context.signal.removeEventListener('abort', onAbort);
}
if (error) {
if (error.killed || error.signal === 'SIGTERM') {
resolve({ success: false, output: stdout, error: `Command timed out after ${timeout}ms` });
@@ -43,6 +46,17 @@ export const shellExecTool: Tool = {
}
resolve({ success: true, output: stdout + (stderr ? `\nstderr: ${stderr}` : '') });
});
const onAbort = () => {
child.kill('SIGTERM');
};
if (context?.signal) {
if (context.signal.aborted) {
onAbort();
} else {
context.signal.addEventListener('abort', onAbort, { once: true });
}
}
});
},
};