From c6e6e81c3cf8c322ecd77ec5e1a10d5992dfbc8c Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Tue, 20 Jan 2026 09:07:25 -0500 Subject: [PATCH] Fix artifact API - use correct parameter and field names Correct the artifact implementation to match Unsandbox API: - Request parameter: return_artifact (boolean, singular) - Response field: artifacts (array, plural) - Data field: content_base64 (not data/content) Changes: - Backend: Changed artifacts to return_artifact in request body - Frontend: Use return_artifact in execute request - Frontend: Look for artifacts array in response (not artifact singleton) - Frontend: Decode content_base64 field (not data/content) - Frontend: Use filename field (primary) over name - Remove debug console.log statements - Update CLAUDE.md with correct API contract The API parameter is singular but response is plural - this is intentional design by Unsandbox API. Request enables artifact collection, response returns array of generated artifacts. --- CLAUDE.md | 31 +++++++++++++++++++------------ app.py | 2 +- templates/chat.html | 32 +++++++++++--------------------- 3 files changed, 31 insertions(+), 34 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index a2928db..4e31ffe 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -232,9 +232,10 @@ lang = un.detect_language("script.py") # Returns "python" OpenCompletion supports artifacts generated during code execution (compiled binaries, images, videos, etc.). **How Artifacts Work**: -- Pass `artifacts: true` in execution requests to enable artifact collection -- Artifacts are returned as **base64-encoded data** directly in the job response payload +- Pass `return_artifact: true` (boolean) in execution requests to enable artifact collection +- Response includes `artifacts` array with base64-encoded data - No separate download endpoint needed - artifacts are embedded in the response +- Note: Parameter is singular `return_artifact` but response field is plural `artifacts` **Artifact Response Format**: ```json @@ -246,15 +247,21 @@ OpenCompletion supports artifacts generated during code execution (compiled bina "exit_code": 0, "artifacts": [ { - "name": "output.png", - "type": "image/png", - "data": "base64string...", + "filename": "output.png", + "mime_type": "image/png", + "content_base64": "iVBORw0KGgoAAAANS...", "size": 12345 } ] } ``` +**Artifact Fields**: +- `filename`: Original filename +- `mime_type`: MIME type (e.g., "image/png", "application/octet-stream") +- `content_base64`: Base64-encoded file content +- `size`: File size in bytes + **Artifact Types**: - **Binaries**: Compiled executables (C, C++, Rust, Go, etc.) - **Images**: PNG, JPG, GIF, SVG generated by code @@ -262,22 +269,22 @@ OpenCompletion supports artifacts generated during code execution (compiled bina - **Text/Data**: JSON, CSV, TXT output files **Frontend Features**: -- Download button decodes base64 and triggers browser download -- View button decodes base64 and displays inline: - - **Images**: Rendered as data URLs +- Download button decodes `content_base64` and triggers browser download +- View button decodes `content_base64` and displays inline: + - **Images**: Rendered as data URLs (`data:image/png;base64,...`) - **Videos**: Rendered as blob URLs with controls - **Text**: Decoded and displayed in formatted `
` blocks
 - View button disabled for binary executables
-- File size and type information displayed
+- File size and MIME type information displayed
 
 #### Frontend Integration
 
 - Add play button (▶) next to copy button on code blocks
-- Execute code when user clicks play button with `artifacts: true` parameter
+- Execute code when user clicks play button with `return_artifact: true` parameter
 - Display execution results inline below code block
 - Show stdout, stderr, and exit_code separately
-- Display artifacts section with download/view buttons
-- Decode base64 artifacts for inline viewing and downloads
+- Display artifact section with download/view buttons
+- Decode `content_base64` field for inline viewing and downloads
 - Images displayed as data URLs, videos as blob URLs
 - Use syntax highlighting for output
 - Handle timeouts gracefully (60s default)
diff --git a/app.py b/app.py
index 127108e..0d4f4fb 100644
--- a/app.py
+++ b/app.py
@@ -1276,7 +1276,7 @@ def proxy_code_execute():
         request_body = {
             "language": language,
             "code": code,
-            "return_artifacts": True,
+            "return_artifact": True,
         }
 
         # Use SDK's internal _make_request for full parameter support
diff --git a/templates/chat.html b/templates/chat.html
index 30cada3..d20318c 100644
--- a/templates/chat.html
+++ b/templates/chat.html
@@ -1708,7 +1708,7 @@ async function executeCodeBlock(code, blockElement, playButton) {
             body: JSON.stringify({
                 language: language,
                 code: code,
-                artifacts: true  // Request artifacts (binaries, images, videos, etc)
+                return_artifact: true  // Request artifacts (binaries, images, videos, etc)
             })
         });
 
@@ -1946,10 +1946,6 @@ function displayExecutionResults(result, resultsContainer, language, code) {
 
     // Handle artifacts (binaries, images, videos, etc.)
     if (artifacts && artifacts.length > 0) {
-        // Debug: log artifact structure
-        console.log('Artifacts received:', artifacts);
-        console.log('First artifact:', artifacts[0]);
-
         const artifactsDiv = document.createElement('div');
         artifactsDiv.style.marginTop = '12px';
         artifactsDiv.style.borderTop = '1px solid var(--border-color)';
@@ -1963,9 +1959,6 @@ function displayExecutionResults(result, resultsContainer, language, code) {
         artifactsDiv.appendChild(artifactsTitle);
 
         artifacts.forEach((artifact, index) => {
-            // Debug: log each artifact
-            console.log(`Artifact ${index}:`, artifact);
-            console.log(`Artifact ${index} keys:`, Object.keys(artifact));
             const artifactItem = document.createElement('div');
             artifactItem.style.marginBottom = '8px';
             artifactItem.style.padding = '8px';
@@ -1976,17 +1969,18 @@ function displayExecutionResults(result, resultsContainer, language, code) {
             const artifactName = document.createElement('div');
             artifactName.style.fontWeight = 'bold';
             artifactName.style.marginBottom = '4px';
-            artifactName.textContent = artifact.name || artifact.filename || `Artifact ${index + 1}`;
+            artifactName.textContent = artifact.filename || artifact.name || `Artifact ${index + 1}`;
             artifactItem.appendChild(artifactName);
 
             // Artifact type/size info
-            if (artifact.type || artifact.size) {
+            const mimeType = artifact.mime_type || artifact.type || '';
+            if (mimeType || artifact.size) {
                 const artifactInfo = document.createElement('div');
                 artifactInfo.style.fontSize = '12px';
                 artifactInfo.style.color = 'var(--text-muted)';
                 artifactInfo.style.marginBottom = '8px';
                 let infoText = '';
-                if (artifact.type) infoText += `Type: ${artifact.type}`;
+                if (mimeType) infoText += `Type: ${mimeType}`;
                 if (artifact.size) infoText += ` | Size: ${formatFileSize(artifact.size)}`;
                 artifactInfo.textContent = infoText;
                 artifactItem.appendChild(artifactInfo);
@@ -1998,7 +1992,6 @@ function displayExecutionResults(result, resultsContainer, language, code) {
             buttonsDiv.style.gap = '8px';
 
             // Determine if artifact is viewable (images, videos, text)
-            const mimeType = artifact.mime_type || artifact.type || '';
             const isImage = mimeType.startsWith('image/');
             const isVideo = mimeType.startsWith('video/');
             const isBinary = mimeType.includes('octet-stream') || mimeType.includes('executable');
@@ -2126,12 +2119,8 @@ function formatFileSize(bytes) {
 // Helper function to download artifact
 async function downloadArtifact(artifact) {
     try {
-        console.log('Download artifact called with:', artifact);
-        console.log('Artifact keys:', Object.keys(artifact));
-
-        // Artifacts come as base64 in the response
-        const base64Data = artifact.data || artifact.content;
-        console.log('Base64 data found:', base64Data ? `yes (${base64Data.length} chars)` : 'no');
+        // Artifacts come as base64 in the response (field: content_base64)
+        const base64Data = artifact.content_base64 || artifact.data || artifact.content;
 
         if (!base64Data) {
             console.error('No base64 data in artifact:', artifact);
@@ -2154,7 +2143,7 @@ async function downloadArtifact(artifact) {
         const url = URL.createObjectURL(blob);
         const a = document.createElement('a');
         a.href = url;
-        a.download = artifact.name || artifact.filename || 'download';
+        a.download = artifact.filename || artifact.name || 'download';
         document.body.appendChild(a);
         a.click();
         document.body.removeChild(a);
@@ -2168,9 +2157,10 @@ async function downloadArtifact(artifact) {
 // Helper function to view artifact inline
 async function viewArtifact(artifact, artifactItem, isImage, isVideo) {
     try {
-        // Artifacts come as base64 in the response
-        const base64Data = artifact.data || artifact.content;
+        // Artifacts come as base64 in the response (field: content_base64)
+        const base64Data = artifact.content_base64 || artifact.data || artifact.content;
         if (!base64Data) {
+            console.error('No base64 data in artifact:', artifact);
             alert('Artifact data not available');
             return;
         }