helpingai
Version:
The official JavaScript/TypeScript library for the HelpingAI API - Advanced AI with Emotional Intelligence
283 lines (274 loc) • 10 kB
JavaScript
;
/**
* Code Interpreter Tool for HelpingAI SDK
*
* This tool provides Python code execution in a sandboxed environment,
* inspired by Qwen-Agent's CodeInterpreter tool.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CodeInterpreterTool = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const child_process_1 = require("child_process");
const uuid_1 = require("uuid");
const base_1 = require("./base");
/**
* Advanced Python code execution sandbox with data science capabilities.
*
* This tool provides a secure environment for executing Python code with built-in
* support for data analysis, visualization, and scientific computing. Features
* automatic plot saving, timeout protection, and comprehensive error handling.
*/
class CodeInterpreterTool extends base_1.BuiltinToolBase {
constructor(config = {}) {
super(config);
this.name = 'code_interpreter';
this.description = 'Execute Python code in a secure sandboxed environment with support for data analysis, visualization, and computation. Includes popular libraries like matplotlib, pandas, numpy, and automatic plot saving.';
this.parameters = {
type: 'object',
properties: {
code: {
type: 'string',
description: 'Python code to execute',
},
},
required: ['code'],
};
this.initialize();
this.timeout = this.config.timeout || 30000; // 30 seconds default
}
/**
* Execute Python code.
*
* @param kwargs - Parameters containing code to execute
* @returns Execution result including stdout, stderr, and generated images
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async execute(kwargs) {
this.validateParameters(kwargs);
const code = kwargs.code;
if (!code.trim()) {
return 'No code provided to execute.';
}
try {
// Check if Python is available
await this.checkPythonAvailability();
// Execute the code
const result = await this.executeCodeSimple(code);
return result;
}
catch (error) {
return `Code execution failed: ${error instanceof Error ? error.message : String(error)}`;
}
}
/**
* Execute code using Node.js child process.
*
* @param code - Python code to execute
* @returns Execution result
*/
async executeCodeSimple(code) {
const scriptFile = path.join(this.workDir, `script_${(0, uuid_1.v4)()}.py`);
// Prepare the code with proper imports and setup
const preparedCode = this.prepareCode(code);
try {
// Write code to file
fs.writeFileSync(scriptFile, preparedCode, 'utf-8');
// Execute the script
const result = await this.runPythonScript(scriptFile);
// Combine results
const resultParts = [];
if (result.stdout.trim()) {
resultParts.push(`stdout:\n\`\`\`\n${result.stdout.trim()}\n\`\`\``);
}
if (result.stderr.trim()) {
resultParts.push(`stderr:\n\`\`\`\n${result.stderr.trim()}\n\`\`\``);
}
// Check for generated images
const imageResults = this.collectGeneratedImages();
if (imageResults.length > 0) {
resultParts.push(...imageResults);
}
if (resultParts.length === 0) {
resultParts.push('Code executed successfully with no output.');
}
return resultParts.join('\n\n');
}
catch (error) {
return `Code execution error: ${error instanceof Error ? error.message : String(error)}`;
}
finally {
// Clean up script file
if (fs.existsSync(scriptFile)) {
fs.unlinkSync(scriptFile);
}
}
}
/**
* Run Python script using child process.
*
* @param scriptFile - Path to Python script file
* @returns Promise with stdout and stderr
*/
runPythonScript(scriptFile) {
return new Promise((resolve, reject) => {
const pythonProcess = (0, child_process_1.spawn)('python3', [scriptFile], {
cwd: this.workDir,
stdio: ['pipe', 'pipe', 'pipe'],
});
let stdout = '';
let stderr = '';
pythonProcess.stdout.on('data', data => {
stdout += data.toString();
});
pythonProcess.stderr.on('data', data => {
stderr += data.toString();
});
// Set timeout
const timeoutId = setTimeout(() => {
pythonProcess.kill('SIGKILL');
reject(new Error(`Code execution timed out after ${this.timeout / 1000} seconds.`));
}, this.timeout);
pythonProcess.on('close', _code => {
clearTimeout(timeoutId);
resolve({ stdout, stderr });
});
pythonProcess.on('error', error => {
clearTimeout(timeoutId);
reject(error);
});
});
}
/**
* Prepare code with necessary imports and setup.
*
* @param code - Original code
* @returns Prepared code with imports
*/
prepareCode(code) {
const workDirEscaped = this.workDir.replace(/\\/g, '\\\\');
const setupCode = `
import sys
import os
import warnings
warnings.filterwarnings('ignore')
# Common imports
try:
import numpy as np
except ImportError:
pass
try:
import pandas as pd
except ImportError:
pass
try:
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('Agg') # Use non-interactive backend
plt.ioff() # Turn off interactive mode
_HAS_MATPLOTLIB = True
except ImportError:
_HAS_MATPLOTLIB = False
try:
import seaborn as sns
except ImportError:
pass
# Set up working directory
work_dir = r"${workDirEscaped}"
os.chdir(work_dir)
# Function to save plots (only if matplotlib is available)
if _HAS_MATPLOTLIB:
def save_plot(filename=None):
if filename is None:
import uuid
filename = f"plot_{uuid.uuid4()}.png"
filepath = os.path.join(work_dir, filename)
plt.savefig(filepath, dpi=150, bbox_inches='tight')
print(f"Plot saved to: {filename}")
return filepath
# Auto-save plots when plt.show() is called
original_show = plt.show
def auto_save_show(*args, **kwargs):
save_plot()
plt.clf() # Clear the figure
plt.show = auto_save_show
`;
return setupCode + '\n' + code;
}
/**
* Collect any images generated during code execution.
*
* @returns List of image result strings
*/
collectGeneratedImages() {
const imageResults = [];
const imageExtensions = ['.png', '.jpg', '.jpeg', '.svg'];
try {
const files = fs.readdirSync(this.workDir);
const imageFiles = files.filter(file => imageExtensions.some(ext => file.toLowerCase().endsWith(ext)));
for (let i = 0; i < imageFiles.length; i++) {
const imageFile = imageFiles[i];
const imagePath = path.join(this.workDir, imageFile);
try {
// For now, just reference the file path
// In a real implementation, you might encode as base64 or serve via URL
imageResults.push(``);
}
catch (error) {
// Skip problematic images
continue;
}
}
}
catch (error) {
// Ignore errors when reading directory
}
return imageResults;
}
/**
* Check if Python is available on the system.
*
* @throws Error if Python is not available
*/
async checkPythonAvailability() {
return new Promise((resolve, reject) => {
const pythonProcess = (0, child_process_1.spawn)('python3', ['--version'], { stdio: 'pipe' });
pythonProcess.on('close', code => {
if (code === 0) {
resolve();
}
else {
reject(new Error('Python3 is not available on this system. Please install Python 3.x to use the code interpreter.'));
}
});
pythonProcess.on('error', () => {
reject(new Error('Python3 is not available on this system. Please install Python 3.x to use the code interpreter.'));
});
});
}
}
exports.CodeInterpreterTool = CodeInterpreterTool;
//# sourceMappingURL=code_interpreter.js.map