extensions/cli/docs/artifact-uploads.md
The artifact upload feature enables Continue agents running in devboxes to upload arbitrary files (screenshots, videos, logs) to agent session storage for review and debugging purposes. The architecture uses a two-step presigned URL pattern for secure, performant uploads.
The artifact upload system uses presigned URLs for direct client-to-S3 uploads rather than proxying files through the backend. This design provides several benefits:
Security: The backend controls who can upload, validates file types/sizes, and enforces storage limits before issuing a presigned URL. The agent cannot bypass these validations.
Performance: Files upload directly from the devbox to S3, avoiding bandwidth costs and latency of routing through the backend API server.
Scalability: The backend doesn't become a bottleneck for file uploads. S3 handles the heavy lifting of data transfer.
Simplicity: Presigned URLs are time-limited (15 minutes), self-contained credentials that require no complex token management.
The agent requests a presigned upload URL from the backend:
Request:
POST /agents/artifacts/upload-url
Authorization: Bearer <CONTINUE_API_KEY>
Content-Type: application/json
{
"agentSessionId": "<session-id>",
"filename": "screenshot.png",
"contentType": "image/png",
"fileSize": 1048576
}
Backend Validation:
Response (if validation passes):
{
"url": "https://s3.amazonaws.com/bucket/sessions/org/abc123/artifacts/screenshot.png?X-Amz-...",
"key": "sessions/org/abc123/artifacts/screenshot.png",
"expiresIn": 900
}
Response (if validation fails):
400 Bad Request
{
"error": "File size exceeds maximum allowed (50MB)"
}
The agent uploads the file directly to S3 using the presigned URL:
Request:
PUT <presigned-url>
Content-Type: image/png
<file-contents>
S3 validates the presigned URL signature and accepts the upload. The backend is not involved in this step.
Artifacts are stored in S3 with the following path structure:
sessions/
user/
<userId>/
<sessionId>/
artifacts/
screenshot.png
video.mp4
debug.log
session.json # Session state (existing)
diff.txt # Git diff (existing)
org/
<organizationId>/
<sessionId>/
artifacts/
...
This structure:
session.json and diff.txt filesThe system validates both file extensions and MIME types:
Images: .png, .jpg, .jpeg, .gif, .webp
Videos: .mp4, .mov, .avi, .webm
Text/Logs: .log, .txt, .json, .xml, .csv, .html
Content types are validated against an allowlist to prevent uploading executable files or other potentially dangerous content.
Two limits are enforced:
ARTIFACT_MAX_FILE_SIZE_MB)ARTIFACT_MAX_TOTAL_SIZE_MB)The backend calculates total storage by summing all files under the session's S3 prefix before issuing presigned URLs. This prevents a single session from consuming excessive storage.
The UploadArtifact tool is available when running with the beta flag:
cn serve --id <agentSessionId> --beta-upload-artifact-tool
Agents can then use the built-in UploadArtifact tool to upload files:
// The agent calls this tool with the file path
{
"name": "UploadArtifact",
"parameters": {
"filePath": "/tmp/screenshot.png"
}
}
The tool will:
Tool Description: "Upload a file (screenshot, video, log) to the session artifacts for user review. Supported formats: images (png, jpg, jpeg, gif, webp), videos (mp4, mov, avi, webm), and text files (log, txt, json, xml, csv, html). Maximum file size: 50MB. If an artifact with the same filename already exists, it will be overwritten with the new file."
Requirements:
--id <agentSessionId> (agent mode)--beta-upload-artifact-tool flagcn login)For custom implementations, use the service directly:
import { services } from "./services/index.js";
const result = await services.artifactUpload.uploadArtifact({
agentSessionId: process.env.AGENT_SESSION_ID,
filePath: "/tmp/screenshot.png",
accessToken: process.env.CONTINUE_API_KEY,
});
if (result.success) {
console.log(`Uploaded: ${result.filename}`);
} else {
console.error(`Failed: ${result.error}`);
}
const results = await services.artifactUpload.uploadArtifacts(
process.env.AGENT_SESSION_ID,
["/tmp/screenshot1.png", "/tmp/screenshot2.png", "/tmp/debug.log"],
process.env.CONTINUE_API_KEY,
);
results.forEach((result) => {
console.log(`${result.filename}: ${result.success ? "✓" : "✗"}`);
});
The CLI requires these environment variables for artifact uploads:
CONTINUE_API_KEY: Bearer token for backend authenticationCONTINUE_API_BASE: API base URL (defaults to https://api.continue.dev/)AGENT_SESSION_ID: The current agent session identifierThese are automatically provided when running in Continue's devbox environment.
All errors are logged and returned with descriptive messages. Failed uploads don't crash the agent - they return an error result that the agent can handle gracefully.
The frontend can list and download artifacts using:
List artifacts:
GET /agents/{agentSessionId}/artifacts
Authorization: Bearer <API_KEY>
Response:
{
"artifacts": [
{
"filename": "screenshot.png",
"size": 1048576,
"sizeFormatted": "1.0 MB",
"lastModified": "2025-12-08T10:30:00Z"
}
]
}
Download artifact:
GET /agents/{agentSessionId}/artifacts/{filename}/download
Authorization: Bearer <API_KEY>
Response:
{
"url": "https://s3.amazonaws.com/...",
"expiresIn": 3600
}
The frontend then uses the presigned download URL to fetch the artifact directly from S3.
../ attacksDecision: Store artifacts as files in S3 without tracking individual files in the database.
Rationale:
Trade-off: Cannot query artifacts across sessions or track metadata without scanning S3.
Decision: If the same filename is uploaded twice, the last upload wins.
Rationale:
Trade-off: No version history for artifacts.
Decision: Use presigned URLs instead of proxying files through backend.
Rationale:
Trade-off: Backend cannot inspect file contents before upload (relies on validation at URL generation).
This document describes the initial artifact upload implementation. Update it when the architecture evolves.