From 41e9e8ae7cf4938ed3487b9c4da5f8da1661f468 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Sun, 30 Nov 2025 06:08:17 -0500 Subject: [PATCH] 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 --- CLAUDE.md | 125 +++++++++++++++++++++++++++++++++++++++++--- templates/chat.html | 77 +++++++++------------------ 2 files changed, 141 insertions(+), 61 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index e9545a7..513d87c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 diff --git a/templates/chat.html b/templates/chat.html index 6807d7e..51316ab 100644 --- a/templates/chat.html +++ b/templates/chat.html @@ -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 = `
${escapeHtml(errorMsg)}
`; - } 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 += `
Language: ${language}
`; } + // Show exit code + if (exitCode !== undefined && exitCode !== null) { + const exitColor = exitCode === 0 ? 'var(--text-success)' : 'var(--text-error)'; + outputHtml += `
Exit Code: ${exitCode}
`; + } + // Show stdout if (actualStdout) { outputHtml += '
Output:
';