Switch code execution to Unsandbox API

- Update CODE_EXEC_URL from code.ai.unturf.com to api.unsandbox.com
- Fix response field handling for Unsandbox API format (flat structure)
- Add exit code display with color coding (green=0, red=error)
- Update displayExecutionResults to handle stdout/stderr/exit_code at top level
- Simplify error handling for timeout/cancelled jobs
- Add comprehensive Unsandbox API documentation to CLAUDE.md
This commit is contained in:
Russell Ballestrini 2025-11-30 06:08:17 -05:00
parent dc263f8613
commit 41e9e8ae7c
2 changed files with 141 additions and 61 deletions

125
CLAUDE.md
View file

@ -67,14 +67,123 @@
3. HTML sanitized with DOMPurify
4. Code blocks enhanced with copy buttons, syntax highlighting, and line numbers
### Code Execution Integration (New)
- Integration with unfirecracker-code-executor service on cammy.foxhop.net
- Service supports 38 working languages with auto-detection
- Endpoints:
- `/execute` - Execute code with specified language
- `/run` - Auto-detect language and execute
- Add play button next to copy button for code blocks
- Display execution results inline below code blocks
### Code Execution Integration
OpenCompletion integrates with the Unsandbox API (https://api.unsandbox.com) for secure code execution in 40+ programming languages.
#### API Endpoints
**Synchronous Execution** (immediate results):
```
POST https://api.unsandbox.com/execute
```
- Executes code immediately and returns results
- Use for quick code snippets and interactive execution
**Asynchronous Execution** (long-running tasks):
```
POST https://api.unsandbox.com/execute/async
```
- Returns job ID for later retrieval
- Use for long-running scripts (up to 15 minutes)
**Auto-Detect Language**:
```
POST https://api.unsandbox.com/run
```
- Automatically detects language from shebang
- Send raw code as request body
- Useful when language is unknown or embedded in script
#### Request Format
```json
{
"language": "python",
"code": "print('Hello, World!')",
"env": {
"VAR_NAME": "value"
},
"network_mode": "zerotrust",
"ttl": 60
}
```
**Parameters**:
- `language` (required): Programming language identifier
- `code` (required): Source code to execute
- `env` (optional): Environment variables as key-value pairs
- `network_mode` (optional): "zerotrust" (default) or "semitrusted"
- `ttl` (optional): Timeout in seconds (1-900, default 60)
#### Response Format
**Success Response**:
```json
{
"success": true,
"stdout": "Hello, World!\n",
"stderr": "",
"exit_code": 0
}
```
**Error Response**:
```json
{
"success": false,
"stdout": "",
"stderr": "SyntaxError: invalid syntax\n",
"exit_code": 1,
"error": "Runtime error occurred"
}
```
**Response Fields**:
- `success` (boolean): True if execution completed without errors
- `stdout` (string): Standard output from the program
- `stderr` (string): Standard error output
- `exit_code` (integer): Program exit status (0 = success, non-zero = error)
- `error` (string, optional): Detailed error message if execution failed
- `detected_language` (string, optional): Language detected by auto-detect endpoint
#### Authentication
Use Bearer token authentication:
```
Authorization: Bearer unsb-sk-xxxx-xxxx-xxxx-xxxx
```
API keys start with `unsb-sk-` prefix.
#### Supported Languages
40+ languages including:
- **Compiled**: C, C++, Rust, Go, Java, C#, Swift
- **Interpreted**: Python, Ruby, JavaScript, PHP, Perl, Lua
- **Scripting**: Bash, PowerShell, Fish
- **Data**: R, Julia, Octave, MATLAB
- **Functional**: Haskell, Scala, Erlang, Elixir
- **Esoteric**: Brainfuck, LOLCODE
- And many more...
#### Frontend Integration
- Add play button (▶) next to copy button on code blocks
- Execute code when user clicks play button
- Display execution results inline below code block
- Show stdout, stderr, and exit_code separately
- Use syntax highlighting for output
- Handle timeouts gracefully (60s default)
- Support language auto-detection for fenced code blocks
#### Security Features
- **Isolated Execution**: Each execution runs in isolated container
- **Network Control**: Zero-trust or semi-trusted network modes
- **Timeout Protection**: Automatic termination after TTL expires
- **Resource Limits**: CPU, memory, and disk quotas enforced
- **Safe Defaults**: Minimal privileges, read-only filesystem (except /tmp)
## Activity YAML Schema

View file

@ -98,7 +98,7 @@
const API_KEY = "dummy-api-key";
const TTS_API_URL = "https://speech.ai.unturf.com/v1/audio/speech";
const VOICES_API_URL = "https://speech.ai.unturf.com/v1/voices";
const CODE_EXEC_URL = "https://code.ai.unturf.com"; // Code execution service URL (served via Caddy)
const CODE_EXEC_URL = "https://api.unsandbox.com"; // Unsandbox code execution API
const room_name = "{{ room_name }}";
// Get username from server (authenticated user's display name or None)
@ -1500,55 +1500,25 @@ async function executeCodeBlock(code, blockElement, playButton) {
}
if (job.status === 'completed') {
const result = job.result;
displayExecutionResults(result, resultsContainer, language, code);
// Unsandbox returns stdout/stderr/exit_code at top level of job response
displayExecutionResults(job, resultsContainer, language, code);
break;
}
// timeout or cancelled - display results and artifact if available
const errorMsg = job.result?.error || 'Execution failed';
const partialOutput = job.result?.partial_output;
// timeout, cancelled, or failed
const errorMsg = job.error || job.status;
// CONFIRMED via testing (make test-artifact): Executor service does NOT
// include artifact field in GET /jobs/{id} response for cancelled jobs
// (exit_code 137 = SIGKILL), even when return_artifact=true was requested.
// Artifacts only included for fully completed jobs (exit_code 0).
//
// The code below checks for artifact anyway in case this gets fixed in the
// future, but currently it will always be undefined for cancelled/timeout.
const artifact = job.artifact || job.result?.artifact;
// Display whatever output we have
displayExecutionResults(job, resultsContainer, language, code);
// Build result object that displayExecutionResults can understand
// For timeout/cancelled, partial_output contains the output before timeout
if (job.result) {
const resultForDisplay = {
stdout: partialOutput || job.result.stdout || '',
stderr: job.result.stderr || '',
artifact: artifact
};
displayExecutionResults(resultForDisplay, resultsContainer, language, code);
// Prepend error message to the results
const errorDiv = document.createElement('div');
errorDiv.style.color = 'var(--text-error)';
errorDiv.style.fontWeight = 'bold';
errorDiv.style.marginBottom = '8px';
errorDiv.textContent = `Execution ${errorMsg}`;
resultsContainer.insertBefore(errorDiv, resultsContainer.firstChild);
// Prepend error message to the results
const errorDiv = document.createElement('div');
errorDiv.style.color = 'var(--text-error)';
errorDiv.style.fontWeight = 'bold';
errorDiv.style.marginBottom = '8px';
errorDiv.textContent = errorMsg;
resultsContainer.insertBefore(errorDiv, resultsContainer.firstChild);
// Add note if there was partial output
if (partialOutput) {
const partialNote = document.createElement('div');
partialNote.style.color = 'var(--text-muted)';
partialNote.style.fontSize = '12px';
partialNote.style.marginBottom = '8px';
partialNote.textContent = '(Output before timeout/cancellation)';
resultsContainer.insertBefore(partialNote, resultsContainer.children[1]);
}
} else {
// No result object at all, just show error
resultsContainer.innerHTML = `<div style="color: var(--text-error); font-weight: bold;">${escapeHtml(errorMsg)}</div>`;
}
break;
}
@ -1579,21 +1549,22 @@ function displayExecutionResults(result, resultsContainer, language, code) {
// Format and display results
let outputHtml = '';
// Handle nested response structure - check if stdout is an object with nested data
let actualStdout = result.stdout;
let actualStderr = result.stderr;
// If stdout is an object (nested response), extract the actual stdout/stderr
if (typeof result.stdout === 'object' && result.stdout !== null) {
actualStdout = result.stdout.stdout || '';
actualStderr = result.stdout.stderr || '';
}
// Unsandbox API returns flat structure: {success, stdout, stderr, exit_code}
const actualStdout = result.stdout || '';
const actualStderr = result.stderr || '';
const exitCode = result.exit_code;
// Show language
if (language) {
outputHtml += `<div style="color: var(--text-info); margin-bottom: 8px;">Language: ${language}</div>`;
}
// Show exit code
if (exitCode !== undefined && exitCode !== null) {
const exitColor = exitCode === 0 ? 'var(--text-success)' : 'var(--text-error)';
outputHtml += `<div style="color: ${exitColor}; margin-bottom: 8px;">Exit Code: ${exitCode}</div>`;
}
// Show stdout
if (actualStdout) {
outputHtml += '<div style="color: var(--text-success); font-weight: bold;">Output:</div>';