UNPKG

@agentics.org/sparc2

Version:

SPARC 2.0 - Autonomous Vector Coding Agent + MCP. SPARC 2.0, vectorized AI code analysis, is an intelligent coding agent framework built to automate and streamline software development. It combines secure execution environments, and version control into

446 lines (407 loc) 13.5 kB
/** * E2B Code Interpreter Implementation * * This module provides a secure sandbox for executing code using the E2B SDK. * It handles code execution, file operations, and package installation. */ import { logMessage } from "../logger.ts"; // Import the E2B SDK import { Sandbox as E2BSandbox } from "npm:@e2b/code-interpreter"; // Re-export the Sandbox type for use in tests export type Sandbox = E2BSandbox; /** * Type definition for execution result */ export interface ExecutionResult { /** Output text from the execution */ text: string; /** Any results or artifacts generated during execution */ results: any[]; /** Error information if execution failed */ error?: { type: string; value: string; } | null; /** Logs from the execution */ logs: { stdout: string[]; stderr: string[]; }; } /** * Options for creating a code interpreter sandbox */ export interface CodeInterpreterOptions { /** Optional API key to use instead of the environment variable */ apiKey?: string; } /** * Options for running code */ export interface RunCodeOptions { /** Whether to stream output */ stream?: boolean; /** Language to use for execution */ language?: "python" | "javascript" | "typescript"; /** Timeout in milliseconds */ timeout?: number; } /** * Create a new sandbox instance * @param options Options for the sandbox * @returns A promise that resolves to the sandbox instance */ export async function createSandbox(options: CodeInterpreterOptions = {}): Promise<Sandbox> { const apiKey = options.apiKey || Deno.env.get("E2B_API_KEY"); if (!apiKey) { throw new Error("E2B_API_KEY is required either in options or as an environment variable"); } try { // Create a new sandbox instance using the factory method // @ts-ignore - Ignore TypeScript errors for API compatibility const sandbox = await E2BSandbox.create({ apiKey }); await logMessage("info", "Created code interpreter sandbox"); return sandbox; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); await logMessage("error", "Failed to create code interpreter sandbox", { error: errorMessage }); throw error; } } /** * Execute code in the sandbox * @param code The code to execute * @param options Options for execution * @returns The execution result */ export async function executeCode( code: string, options: { stream?: boolean; language?: "python" | "javascript" | "typescript"; timeout?: number; } = {}, ): Promise<ExecutionResult> { const sandbox = await createSandbox(); try { // Prepare the code based on the language let preparedCode = code; const language = options.language || "python"; if (language === "typescript" && !code.includes("///@ts-nocheck")) { // Add ts-nocheck to avoid TypeScript errors in the sandbox preparedCode = "///@ts-nocheck\n" + code; } if ( language === "python" && !code.trim().startsWith("!pip") && !code.trim().startsWith("import") ) { // For Python, ensure basic imports are available preparedCode = "import sys\nimport os\n" + preparedCode; } // Set up execution options const execOptions: any = { language, }; if (options.stream) { execOptions.onStdout = (data: any) => console.log("[stdout]", data); execOptions.onStderr = (data: any) => console.error("[stderr]", data); } // Execute the code with timeout let execution: any; if (options.timeout) { // Create a promise that rejects after the timeout const timeoutPromise = new Promise<never>((_, reject) => { setTimeout( () => reject(new Error(`Execution timed out after ${options.timeout}ms`)), options.timeout, ); }); // Race the execution against the timeout // @ts-ignore - Ignore TypeScript errors for API compatibility execution = await Promise.race([ sandbox.runCode(preparedCode, execOptions), timeoutPromise, ]); } else { // @ts-ignore - Ignore TypeScript errors for API compatibility execution = await sandbox.runCode(preparedCode, execOptions); } // Format the result to match our ExecutionResult interface const result: ExecutionResult = { text: execution.text || "", results: execution.results || [], error: execution.error ? { type: "error", value: typeof execution.error === "string" ? execution.error : JSON.stringify(execution.error), } : null, logs: { stdout: Array.isArray(execution.logs?.stdout) ? execution.logs.stdout : (execution.logs?.stdout ? [execution.logs.stdout] : []), stderr: Array.isArray(execution.logs?.stderr) ? execution.logs.stderr : (execution.logs?.stderr ? [execution.logs.stderr] : []), }, }; // Log the execution result const isError = result.error !== null && result.error !== undefined; await logMessage( isError ? "error" : "info", `Code execution ${isError ? "failed" : "completed"}`, { error: isError && result.error ? result.error.value : undefined, outputLength: result.text.length, resultsCount: result.results.length, }, ); return result; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); await logMessage("error", "Code execution failed", { error: errorMessage }); // Return an error result return { text: "", results: [], error: { type: "error", value: errorMessage, }, logs: { stdout: [], stderr: [errorMessage], }, }; } finally { // Always close the sandbox to free resources // @ts-ignore - Ignore TypeScript errors for API compatibility await sandbox.kill(); } } /** * Execute code from a file * @param filePath Path to the file to execute * @param options Options for execution * @returns The execution result */ export async function executeFile( filePath: string, options: { stream?: boolean; language?: "python" | "javascript" | "typescript"; timeout?: number; } = {}, ): Promise<ExecutionResult> { try { // Read the file content const content = await Deno.readTextFile(filePath); // Determine language from file extension if not specified if (!options.language) { const extension = filePath.split(".").pop()?.toLowerCase(); if (extension === "py") { options.language = "python"; } else if (extension === "js") { options.language = "javascript"; } else if (extension === "ts") { options.language = "typescript"; } } // Execute the file content return await executeCode(content, options); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); await logMessage("error", `Failed to execute file: ${filePath}`, { error: errorMessage }); // Return an error result return { text: "", results: [], error: { type: "error", value: errorMessage, }, logs: { stdout: [], stderr: [errorMessage], }, }; } } /** * Write a file in the sandbox * @param path Path in the sandbox * @param content Content to write * @param options Options for the sandbox */ export async function writeFile( path: string, content: string, options: CodeInterpreterOptions = {}, existingSandbox?: Sandbox, ): Promise<void> { const sandbox = existingSandbox || await createSandbox(options); try { // @ts-ignore - Ignore TypeScript errors for API compatibility await sandbox.files.write(path, content); await logMessage("info", `File written to sandbox: ${path}`, { contentLength: content.length }); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); await logMessage("error", `Failed to write file to sandbox: ${path}`, { error: errorMessage }); throw error; } finally { if (!existingSandbox) { // @ts-ignore - Ignore TypeScript errors for API compatibility await sandbox.kill(); } } } /** * Read a file from the sandbox * @param path Path in the sandbox * @param options Options for the sandbox * @returns The file content */ export async function readFile( path: string, options: CodeInterpreterOptions = {}, existingSandbox?: Sandbox, ): Promise<string> { const sandbox = existingSandbox || await createSandbox(options); try { // @ts-ignore - Ignore TypeScript errors for API compatibility const content = await sandbox.files.read(path); await logMessage("info", `File read from sandbox: ${path}`, { contentLength: content.length }); return content; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); await logMessage("error", `Failed to read file from sandbox: ${path}`, { error: errorMessage }); throw error; } finally { if (!existingSandbox) { // @ts-ignore - Ignore TypeScript errors for API compatibility await sandbox.kill(); } } } /** * List files in the sandbox * @param path Path in the sandbox * @param options Options for the sandbox * @returns Array of file names */ export async function listFiles( path: string, options: CodeInterpreterOptions = {}, existingSandbox?: Sandbox, ): Promise<string[]> { const sandbox = existingSandbox || await createSandbox(options); try { // @ts-ignore - Ignore TypeScript errors for API compatibility const entries = await sandbox.files.list(path); const files = entries.map((entry: any) => entry.name || entry.path || String(entry)); await logMessage("info", `Listed files in sandbox: ${path}`, { fileCount: entries.length }); return files; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); await logMessage("error", `Failed to list files in sandbox: ${path}`, { error: errorMessage }); throw error; } finally { if (!existingSandbox) { // @ts-ignore - Ignore TypeScript errors for API compatibility await sandbox.kill(); } } } /** * Install packages in the sandbox * @param packages Array of packages to install * @param language The language for which to install packages * @param options Options for the sandbox * @returns The execution result */ export async function installPackages( packages: string[], language: "python" | "javascript" | "typescript" = "python", options: CodeInterpreterOptions = {}, ): Promise<ExecutionResult> { const sandbox = await createSandbox(options); try { let installCommand: string; if (language === "python") { installCommand = `!pip install ${packages.join(" ")}`; // Python uses !pip install } else if (language === "javascript" || language === "typescript") { installCommand = `const { execSync } = require('child_process'); try { console.log('Installing packages: ${packages.join(", ")}'); execSync('npm install ${packages.join(" ")}', { stdio: 'inherit' }); console.log('Packages installed successfully'); } catch (error) { console.error('Failed to install packages:', error.message); }`; } else { throw new Error(`Unsupported language: ${language}`); } // @ts-ignore - Ignore TypeScript errors for API compatibility const execution = await sandbox.runCode(installCommand, { language, onStdout: (data: any) => console.log("[stdout]", data), onStderr: (data: any) => console.error("[stderr]", data), }); // Format the result to match our ExecutionResult interface const result: ExecutionResult = { text: execution.text || "", results: execution.results || [], error: execution.error ? { type: "error", value: typeof execution.error === "string" ? execution.error : JSON.stringify(execution.error), } : null, logs: { stdout: Array.isArray(execution.logs?.stdout) ? execution.logs.stdout : (execution.logs?.stdout ? [execution.logs.stdout] : []), stderr: Array.isArray(execution.logs?.stderr) ? execution.logs.stderr : (execution.logs?.stderr ? [execution.logs.stderr] : []), }, }; const isError = result.error !== null && result.error !== undefined; await logMessage( isError ? "error" : "info", `Package installation ${isError ? "failed" : "completed"}`, { packages, language, error: isError && result.error ? result.error.value : undefined, }, ); return result; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); await logMessage("error", "Package installation failed", { error: errorMessage, packages, language, }); // Return an error result return { text: "", results: [], error: { type: "error", value: errorMessage, }, logs: { stdout: [], stderr: [errorMessage], }, }; } finally { // @ts-ignore - Ignore TypeScript errors for API compatibility await sandbox.kill(); } }