Each tool now provides comprehensive documentation including parameter schemas and concrete <tool_call> XML examples. This helps smaller LLMs understand the exact format required for tool invocation.
231 lines
6.4 KiB
TypeScript
231 lines
6.4 KiB
TypeScript
import * as plugins from './plugins.js';
|
|
import * as interfaces from './smartagent.interfaces.js';
|
|
import { BaseToolWrapper } from './smartagent.tools.base.js';
|
|
|
|
/**
|
|
* Shell tool for executing commands securely
|
|
* Wraps @push.rocks/smartshell with execSpawn for safety (no shell injection)
|
|
*/
|
|
export class ShellTool extends BaseToolWrapper {
|
|
public name = 'shell';
|
|
public description =
|
|
'Execute shell commands securely. Uses execSpawn (shell:false) to prevent command injection.';
|
|
|
|
public actions: interfaces.IToolAction[] = [
|
|
{
|
|
name: 'execute',
|
|
description:
|
|
'Execute a command with arguments (secure, no shell injection possible). Command and args are passed separately.',
|
|
parameters: {
|
|
type: 'object',
|
|
properties: {
|
|
command: {
|
|
type: 'string',
|
|
description: 'The command to execute (e.g., "ls", "cat", "grep", "node")',
|
|
},
|
|
args: {
|
|
type: 'array',
|
|
items: { type: 'string' },
|
|
description: 'Array of arguments (each argument is properly escaped)',
|
|
},
|
|
cwd: { type: 'string', description: 'Working directory for the command' },
|
|
timeout: { type: 'number', description: 'Timeout in milliseconds' },
|
|
env: {
|
|
type: 'object',
|
|
description: 'Additional environment variables (key-value pairs)',
|
|
},
|
|
},
|
|
required: ['command'],
|
|
},
|
|
},
|
|
{
|
|
name: 'which',
|
|
description: 'Check if a command exists and get its path',
|
|
parameters: {
|
|
type: 'object',
|
|
properties: {
|
|
command: { type: 'string', description: 'Command name to look up (e.g., "node", "git")' },
|
|
},
|
|
required: ['command'],
|
|
},
|
|
},
|
|
];
|
|
|
|
private smartshell!: plugins.smartshell.Smartshell;
|
|
|
|
public async initialize(): Promise<void> {
|
|
this.smartshell = new plugins.smartshell.Smartshell({
|
|
executor: 'bash',
|
|
});
|
|
this.isInitialized = true;
|
|
}
|
|
|
|
public async cleanup(): Promise<void> {
|
|
this.isInitialized = false;
|
|
}
|
|
|
|
public async execute(
|
|
action: string,
|
|
params: Record<string, unknown>
|
|
): Promise<interfaces.IToolExecutionResult> {
|
|
this.validateAction(action);
|
|
this.ensureInitialized();
|
|
|
|
try {
|
|
switch (action) {
|
|
case 'execute': {
|
|
const command = params.command as string;
|
|
const args = (params.args as string[]) || [];
|
|
|
|
// Build options
|
|
const options: {
|
|
timeout?: number;
|
|
env?: NodeJS.ProcessEnv;
|
|
cwd?: string;
|
|
} = {};
|
|
|
|
if (params.timeout) {
|
|
options.timeout = params.timeout as number;
|
|
}
|
|
|
|
if (params.env) {
|
|
options.env = {
|
|
...process.env,
|
|
...(params.env as NodeJS.ProcessEnv),
|
|
};
|
|
}
|
|
|
|
// Use execSpawn for security - no shell injection possible
|
|
const result = await this.smartshell.execSpawn(command, args, options);
|
|
|
|
return {
|
|
success: result.exitCode === 0,
|
|
result: {
|
|
command,
|
|
args,
|
|
exitCode: result.exitCode,
|
|
stdout: result.stdout,
|
|
stderr: result.stderr,
|
|
signal: result.signal,
|
|
},
|
|
};
|
|
}
|
|
|
|
case 'which': {
|
|
try {
|
|
const commandPath = await plugins.smartshell.which(params.command as string);
|
|
return {
|
|
success: true,
|
|
result: {
|
|
command: params.command,
|
|
path: commandPath,
|
|
exists: true,
|
|
},
|
|
};
|
|
} catch {
|
|
return {
|
|
success: true,
|
|
result: {
|
|
command: params.command,
|
|
path: null,
|
|
exists: false,
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
default:
|
|
return {
|
|
success: false,
|
|
error: `Unknown action: ${action}`,
|
|
};
|
|
}
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : String(error),
|
|
};
|
|
}
|
|
}
|
|
|
|
public getToolExplanation(): string {
|
|
return `## Tool: shell
|
|
Execute shell commands securely. Uses execSpawn (shell:false) to prevent command injection.
|
|
|
|
### Actions:
|
|
|
|
**execute** - Execute a command with arguments (secure, no shell injection possible)
|
|
Parameters:
|
|
- command (required): The command to execute (e.g., "ls", "cat", "grep", "node")
|
|
- args (optional): Array of arguments (each argument is properly escaped)
|
|
- cwd (optional): Working directory for the command
|
|
- timeout (optional): Timeout in milliseconds
|
|
- env (optional): Additional environment variables (key-value object)
|
|
|
|
Example - List files:
|
|
<tool_call>
|
|
<tool>shell</tool>
|
|
<action>execute</action>
|
|
<params>{"command": "ls", "args": ["-la", "/path/to/dir"]}</params>
|
|
</tool_call>
|
|
|
|
Example - Run Node script:
|
|
<tool_call>
|
|
<tool>shell</tool>
|
|
<action>execute</action>
|
|
<params>{"command": "node", "args": ["script.js"], "cwd": "/path/to/project"}</params>
|
|
</tool_call>
|
|
|
|
Example - Search in files:
|
|
<tool_call>
|
|
<tool>shell</tool>
|
|
<action>execute</action>
|
|
<params>{"command": "grep", "args": ["-r", "pattern", "src/"]}</params>
|
|
</tool_call>
|
|
|
|
**which** - Check if a command exists and get its path
|
|
Parameters:
|
|
- command (required): Command name to look up (e.g., "node", "git")
|
|
|
|
Example:
|
|
<tool_call>
|
|
<tool>shell</tool>
|
|
<action>which</action>
|
|
<params>{"command": "node"}</params>
|
|
</tool_call>
|
|
`;
|
|
}
|
|
|
|
public getCallSummary(action: string, params: Record<string, unknown>): string {
|
|
switch (action) {
|
|
case 'execute': {
|
|
const command = params.command as string;
|
|
const args = (params.args as string[]) || [];
|
|
const fullCommand = [command, ...args].join(' ');
|
|
let summary = `Execute: ${fullCommand}`;
|
|
|
|
if (params.cwd) {
|
|
summary += ` (in ${params.cwd})`;
|
|
}
|
|
|
|
if (params.timeout) {
|
|
summary += ` [timeout: ${params.timeout}ms]`;
|
|
}
|
|
|
|
if (params.env && Object.keys(params.env as object).length > 0) {
|
|
const envKeys = Object.keys(params.env as object).join(', ');
|
|
summary += ` [env: ${envKeys}]`;
|
|
}
|
|
|
|
return summary;
|
|
}
|
|
|
|
case 'which':
|
|
return `Check if command "${params.command}" exists and get its path`;
|
|
|
|
default:
|
|
return `Unknown action: ${action}`;
|
|
}
|
|
}
|
|
}
|