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:
parent
dc263f8613
commit
41e9e8ae7c
2 changed files with 141 additions and 61 deletions
125
CLAUDE.md
125
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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>';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue