feat: make bash tool timeout optional and configurable

- Add optional timeout parameter (in seconds) to bash tool
- No default timeout - commands run until completion unless specified
- Agent can provide timeout when needed for long-running commands
- Update README to reflect optional timeout
This commit is contained in:
Mario Zechner 2025-11-12 23:53:27 +01:00
parent 60cea11f37
commit 29900ce647
2 changed files with 20 additions and 10 deletions

View file

@ -320,7 +320,7 @@ Write content to a file. Creates the file if it doesn't exist, overwrites if it
Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits. Returns an error if the text appears multiple times or isn't found. Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits. Returns an error if the text appears multiple times or isn't found.
**bash** **bash**
Execute a bash command in the current working directory. Returns stdout and stderr. Commands run with a 30 second timeout. Execute a bash command in the current working directory. Returns stdout and stderr. Optionally accepts a `timeout` parameter (in seconds) - no default timeout.
### MCP & Adding Your Own Tools ### MCP & Adding Your Own Tools

View file

@ -4,15 +4,20 @@ import { spawn } from "child_process";
const bashSchema = Type.Object({ const bashSchema = Type.Object({
command: Type.String({ description: "Bash command to execute" }), command: Type.String({ description: "Bash command to execute" }),
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
}); });
export const bashTool: AgentTool<typeof bashSchema> = { export const bashTool: AgentTool<typeof bashSchema> = {
name: "bash", name: "bash",
label: "bash", label: "bash",
description: description:
"Execute a bash command in the current working directory. Returns stdout and stderr. Commands run with a 30 second timeout.", "Execute a bash command in the current working directory. Returns stdout and stderr. Optionally provide a timeout in seconds.",
parameters: bashSchema, parameters: bashSchema,
execute: async (_toolCallId: string, { command }: { command: string }, signal?: AbortSignal) => { execute: async (
_toolCallId: string,
{ command, timeout }: { command: string; timeout?: number },
signal?: AbortSignal,
) => {
return new Promise((resolve, _reject) => { return new Promise((resolve, _reject) => {
const child = spawn("sh", ["-c", command], { const child = spawn("sh", ["-c", command], {
detached: true, detached: true,
@ -23,11 +28,14 @@ export const bashTool: AgentTool<typeof bashSchema> = {
let stderr = ""; let stderr = "";
let timedOut = false; let timedOut = false;
// Set timeout // Set timeout if provided
const timeout = setTimeout(() => { let timeoutHandle: NodeJS.Timeout | undefined;
timedOut = true; if (timeout !== undefined && timeout > 0) {
onAbort(); timeoutHandle = setTimeout(() => {
}, 30000); timedOut = true;
onAbort();
}, timeout * 1000);
}
// Collect stdout // Collect stdout
if (child.stdout) { if (child.stdout) {
@ -53,7 +61,9 @@ export const bashTool: AgentTool<typeof bashSchema> = {
// Handle process exit // Handle process exit
child.on("close", (code) => { child.on("close", (code) => {
clearTimeout(timeout); if (timeoutHandle) {
clearTimeout(timeoutHandle);
}
if (signal) { if (signal) {
signal.removeEventListener("abort", onAbort); signal.removeEventListener("abort", onAbort);
} }
@ -79,7 +89,7 @@ export const bashTool: AgentTool<typeof bashSchema> = {
output += stderr; output += stderr;
} }
if (output) output += "\n\n"; if (output) output += "\n\n";
output += "Command timed out after 30 seconds"; output += `Command timed out after ${timeout} seconds`;
resolve({ content: [{ type: "text", text: `Command failed\n\n${output}` }], details: undefined }); resolve({ content: [{ type: "text", text: `Command failed\n\n${output}` }], details: undefined });
return; return;
} }