generateRunCodeSandbox function
Generates the _runCodeSandbox method.
Implementation
String generateRunCodeSandbox() {
return '''
Future<String?> _runCodeSandbox(String userCode, int timeoutSeconds) async {
io.Process? process;
io.Directory? tempDir;
try {
final wrapper = _buildJsWrapper(userCode);
// Security: Use unpredictable directory name with timestamp + random suffix
final timestamp = DateTime.now().millisecondsSinceEpoch;
final random = math.Random.secure();
final suffix = List.generate(8, (_) => random.nextInt(16).toRadixString(16)).join();
tempDir = await io.Directory.systemTemp.createTemp('mcp_sandbox_\${timestamp}_\${suffix}_');
final scriptFile = io.File('\${tempDir.path}/sandbox.js');
// Set restrictive permissions (owner read/write only)
if (io.Platform.isLinux || io.Platform.isMacOS) {
await scriptFile.create(recursive: true);
await io.Process.run('chmod', ['700', tempDir.path]);
await io.Process.run('chmod', ['600', scriptFile.path]);
}
await scriptFile.writeAsString(wrapper);
process = await io.Process.start(
'node',
[
'--max-old-space-size=64',
'--no-addons',
'--frozen-intrinsics',
scriptFile.path,
],
);
} catch (e) {
await tempDir?.delete(recursive: true);
throw StateError('Code mode requires Node.js to be installed');
}
try {
final resultCompleter = Completer<String?>();
final errorCompleter = Completer<String>();
process.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((line) {
if (line.trim().isEmpty) return;
try {
final msg = jsonDecode(line) as Map<String, dynamic>;
final type = msg['type'] as String?;
if (type == 'call') {
final callId = msg['callId'] as String;
final toolName = msg['tool'] as String;
final args = (msg['args'] as Map<String, dynamic>?) ?? <String, dynamic>{};
_dispatchCodeModeToolCall(toolName, args).then((resultJson) {
process?.stdin.writeln(jsonEncode({
'type': 'result',
'callId': callId,
'data': resultJson,
}));
}).catchError((e, st) {
if (_logErrors) {
io.stderr.writeln('[easy_api] _dispatchCodeModeToolCall(\$toolName): \$e');
io.stderr.writeln(st);
io.stderr.flush(); // fire-and-forget; callback is not async
}
process?.stdin.writeln(jsonEncode({
'type': 'result',
'callId': callId,
'data': null,
'error': 'An error occurred while processing the request.',
}));
});
} else if (type == 'done') {
final result = msg['result'];
if (result == null) {
resultCompleter.complete(null);
} else if (result is String) {
resultCompleter.complete(result);
} else {
resultCompleter.complete(jsonEncode(result));
}
} else if (type == 'error') {
errorCompleter.complete(msg['message'] as String? ?? 'Unknown error');
}
} catch (_) {
// Ignore non-JSON lines
}
});
// Wait for result, error, or timeout
final timeoutFuture = Future.delayed(
Duration(seconds: timeoutSeconds),
() => throw StateError('Code execution timed out after \$timeoutSeconds seconds'),
);
final result = await Future.any<String?>([
resultCompleter.future,
errorCompleter.future.then((e) => throw StateError('Code execution error: \$e')),
timeoutFuture,
]);
return result;
} finally {
// Graceful shutdown: process is guaranteed to be non-null here
// (otherwise process.stdout would have thrown earlier)
final proc = process;
final dir = tempDir;
try {
// First, check if process already exited naturally
await proc.exitCode.timeout(
const Duration(milliseconds: 100),
);
} catch (_) {
// Process still running, begin graceful shutdown
proc.kill(io.ProcessSignal.sigterm);
try {
// Wait up to 2 seconds for graceful shutdown
await proc.exitCode.timeout(
const Duration(seconds: 2),
onTimeout: () {
// Process didn't exit, force kill
proc.kill(io.ProcessSignal.sigkill);
return -1;
},
);
} catch (_) {
// Error during exit code wait - attempt force kill as fallback
proc.kill(io.ProcessSignal.sigkill);
}
}
await dir.delete(recursive: true);
}
}''';
}