192 lines
5.4 KiB
TypeScript
192 lines
5.4 KiB
TypeScript
|
|
import * as plugins from './plugins.js';
|
||
|
|
import * as interfaces from './smartagent.interfaces.js';
|
||
|
|
import { BaseToolWrapper } from './smartagent.tools.base.js';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Deno permission types for sandboxed code execution
|
||
|
|
*/
|
||
|
|
export type TDenoPermission =
|
||
|
|
| 'all'
|
||
|
|
| 'env'
|
||
|
|
| 'ffi'
|
||
|
|
| 'hrtime'
|
||
|
|
| 'net'
|
||
|
|
| 'read'
|
||
|
|
| 'run'
|
||
|
|
| 'sys'
|
||
|
|
| 'write';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Deno tool for executing TypeScript/JavaScript code in a sandboxed environment
|
||
|
|
* Wraps @push.rocks/smartdeno
|
||
|
|
*/
|
||
|
|
export class DenoTool extends BaseToolWrapper {
|
||
|
|
public name = 'deno';
|
||
|
|
public description =
|
||
|
|
'Execute TypeScript/JavaScript code in a sandboxed Deno environment with fine-grained permission control';
|
||
|
|
|
||
|
|
public actions: interfaces.IToolAction[] = [
|
||
|
|
{
|
||
|
|
name: 'execute',
|
||
|
|
description:
|
||
|
|
'Execute TypeScript/JavaScript code and return stdout/stderr. Code runs in Deno sandbox with specified permissions.',
|
||
|
|
parameters: {
|
||
|
|
type: 'object',
|
||
|
|
properties: {
|
||
|
|
code: {
|
||
|
|
type: 'string',
|
||
|
|
description: 'TypeScript/JavaScript code to execute',
|
||
|
|
},
|
||
|
|
permissions: {
|
||
|
|
type: 'array',
|
||
|
|
items: {
|
||
|
|
type: 'string',
|
||
|
|
enum: ['all', 'env', 'ffi', 'hrtime', 'net', 'read', 'run', 'sys', 'write'],
|
||
|
|
},
|
||
|
|
description:
|
||
|
|
'Deno permissions to grant. Default: none (fully sandboxed). Options: all, env, net, read, write, run, sys, ffi, hrtime',
|
||
|
|
},
|
||
|
|
},
|
||
|
|
required: ['code'],
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: 'executeWithResult',
|
||
|
|
description:
|
||
|
|
'Execute code that outputs JSON on the last line of stdout. The JSON is parsed and returned as the result.',
|
||
|
|
parameters: {
|
||
|
|
type: 'object',
|
||
|
|
properties: {
|
||
|
|
code: {
|
||
|
|
type: 'string',
|
||
|
|
description:
|
||
|
|
'Code that console.logs a JSON value on the final line. This JSON will be parsed and returned.',
|
||
|
|
},
|
||
|
|
permissions: {
|
||
|
|
type: 'array',
|
||
|
|
items: {
|
||
|
|
type: 'string',
|
||
|
|
enum: ['all', 'env', 'ffi', 'hrtime', 'net', 'read', 'run', 'sys', 'write'],
|
||
|
|
},
|
||
|
|
description: 'Deno permissions to grant',
|
||
|
|
},
|
||
|
|
},
|
||
|
|
required: ['code'],
|
||
|
|
},
|
||
|
|
},
|
||
|
|
];
|
||
|
|
|
||
|
|
private smartdeno!: plugins.smartdeno.SmartDeno;
|
||
|
|
|
||
|
|
public async initialize(): Promise<void> {
|
||
|
|
this.smartdeno = new plugins.smartdeno.SmartDeno();
|
||
|
|
await this.smartdeno.start();
|
||
|
|
this.isInitialized = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async cleanup(): Promise<void> {
|
||
|
|
if (this.smartdeno) {
|
||
|
|
await this.smartdeno.stop();
|
||
|
|
}
|
||
|
|
this.isInitialized = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async execute(
|
||
|
|
action: string,
|
||
|
|
params: Record<string, unknown>
|
||
|
|
): Promise<interfaces.IToolExecutionResult> {
|
||
|
|
this.validateAction(action);
|
||
|
|
this.ensureInitialized();
|
||
|
|
|
||
|
|
try {
|
||
|
|
const code = params.code as string;
|
||
|
|
const permissions = (params.permissions as TDenoPermission[]) || [];
|
||
|
|
|
||
|
|
// Execute the script
|
||
|
|
const result = await this.smartdeno.executeScript(code, {
|
||
|
|
permissions,
|
||
|
|
});
|
||
|
|
|
||
|
|
switch (action) {
|
||
|
|
case 'execute': {
|
||
|
|
return {
|
||
|
|
success: result.exitCode === 0,
|
||
|
|
result: {
|
||
|
|
exitCode: result.exitCode,
|
||
|
|
stdout: result.stdout,
|
||
|
|
stderr: result.stderr,
|
||
|
|
permissions,
|
||
|
|
},
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
case 'executeWithResult': {
|
||
|
|
if (result.exitCode !== 0) {
|
||
|
|
return {
|
||
|
|
success: false,
|
||
|
|
error: `Script failed with exit code ${result.exitCode}: ${result.stderr}`,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parse the last line of stdout as JSON
|
||
|
|
const lines = result.stdout.trim().split('\n');
|
||
|
|
const lastLine = lines[lines.length - 1];
|
||
|
|
|
||
|
|
try {
|
||
|
|
const parsedResult = JSON.parse(lastLine);
|
||
|
|
return {
|
||
|
|
success: true,
|
||
|
|
result: {
|
||
|
|
parsed: parsedResult,
|
||
|
|
stdout: result.stdout,
|
||
|
|
stderr: result.stderr,
|
||
|
|
},
|
||
|
|
};
|
||
|
|
} catch (parseError) {
|
||
|
|
return {
|
||
|
|
success: false,
|
||
|
|
error: `Failed to parse JSON from last line of output: ${lastLine}`,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
default:
|
||
|
|
return {
|
||
|
|
success: false,
|
||
|
|
error: `Unknown action: ${action}`,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
return {
|
||
|
|
success: false,
|
||
|
|
error: error instanceof Error ? error.message : String(error),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public getCallSummary(action: string, params: Record<string, unknown>): string {
|
||
|
|
const code = params.code as string;
|
||
|
|
const permissions = (params.permissions as string[]) || [];
|
||
|
|
|
||
|
|
// Create a preview of the code (first 100 chars)
|
||
|
|
const codePreview = code.length > 100 ? code.substring(0, 100) + '...' : code;
|
||
|
|
// Escape newlines for single-line display
|
||
|
|
const cleanPreview = codePreview.replace(/\n/g, '\\n');
|
||
|
|
|
||
|
|
const permissionInfo = permissions.length > 0
|
||
|
|
? ` [permissions: ${permissions.join(', ')}]`
|
||
|
|
: ' [sandboxed - no permissions]';
|
||
|
|
|
||
|
|
switch (action) {
|
||
|
|
case 'execute':
|
||
|
|
return `Execute Deno code${permissionInfo}: "${cleanPreview}"`;
|
||
|
|
|
||
|
|
case 'executeWithResult':
|
||
|
|
return `Execute Deno code and parse JSON result${permissionInfo}: "${cleanPreview}"`;
|
||
|
|
|
||
|
|
default:
|
||
|
|
return `Unknown action: ${action}`;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|