coding-agent
Version:
An AI coding agent using @agenite framework
146 lines (130 loc) • 3.98 kB
text/typescript
import { Tool, ToolResponse } from '@agenite/tool';
import { spawn } from 'child_process';
interface CommandInput {
command: string;
cwd?: string;
timeout?: number;
}
export function createCommandRunnerTool(): Tool<CommandInput> {
const commandCache = new Map<
string,
{
data: string;
timestamp: number;
success: boolean;
}
>();
return new Tool({
name: 'command_runner',
description:
'Executes shell commands on the system. Use with caution as improper commands can cause unintended behavior.',
inputSchema: {
type: 'object',
properties: {
command: {
type: 'string',
description:
'The command to execute. Ensure it is a valid shell command.',
},
cwd: {
type: 'string',
description: 'Optional working directory for the command.',
},
timeout: {
type: 'number',
description: 'Optional timeout in milliseconds (default: 30,000 ms).',
},
},
required: ['command'],
},
async execute({ input }) {
const startTime = Date.now();
const cacheKey = `${input.command}-${input.cwd || ''}-${input.timeout || 30000}`;
// Validate command input
if (!input.command || typeof input.command !== 'string') {
return {
isError: true,
data: 'Invalid command input. Must be a non-empty string.',
duration: 0,
};
}
// Check cache for recent execution results
const cached = commandCache.get(cacheKey);
if (cached && startTime - cached.timestamp < 60000) {
return {
isError: !cached.success,
data: cached.data,
duration: 0,
};
}
return new Promise<ToolResponse>((resolve) => {
let output = '';
let errorOutput = '';
const [cmd, ...args] = input.command.split(' ');
if (!cmd) {
return resolve({
isError: true,
data: 'Invalid command input. Must be a non-empty string.',
duration: Date.now() - startTime,
});
}
const child = spawn(cmd, args, {
cwd: process.cwd() + '/' + 'output',
shell: true,
});
const timeout = input.timeout || 30000;
const timer = setTimeout(() => {
child.kill('SIGTERM');
resolve({
isError: true,
data: 'Command timed out',
duration: Date.now() - startTime,
});
}, timeout);
// Collect stdout
child.stdout.on('data', (data) => {
output += data.toString();
});
// Collect stderr
child.stderr.on('data', (data) => {
errorOutput += data.toString();
});
// On command completion
child.on('close', (code) => {
clearTimeout(timer);
const result = {
success: code === 0,
data: (output || errorOutput || 'No output captured').replace(
process.cwd() + '/output',
''
),
duration: Date.now() - startTime,
};
// Cache result
commandCache.set(cacheKey, {
data: result.data,
timestamp: startTime,
success: result.success,
});
resolve(result as unknown as ToolResponse);
});
// Catch errors with the subprocess
child.on('error', (err) => {
clearTimeout(timer);
resolve({
isError: true,
data: `Error executing command: ${err.message}`,
duration: Date.now() - startTime,
});
});
}).catch((err) => {
console.error('Unexpected error:', err);
return {
isError: true,
data: 'Unexpected error while executing the command',
duration: Date.now() - startTime,
};
});
},
});
}