diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts
index c4b7818..9edff1c 100644
--- a/apps/web/next-env.d.ts
+++ b/apps/web/next-env.d.ts
@@ -1,6 +1,6 @@
///
///
-import "./.next/dev/types/routes.d.ts";
+import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/apps/web/src/app/api/agents/[id]/route.ts b/apps/web/src/app/api/agents/[id]/route.ts
index 1870d42..f70ebb5 100644
--- a/apps/web/src/app/api/agents/[id]/route.ts
+++ b/apps/web/src/app/api/agents/[id]/route.ts
@@ -60,6 +60,7 @@ export async function GET(_request: NextRequest, context: RouteContext) {
id: true,
name: true,
description: true,
+ parameters: true,
package: {
select: {
npmPackageName: true,
diff --git a/apps/web/src/app/api/collections/[id]/tools/route.ts b/apps/web/src/app/api/collections/[id]/tools/route.ts
index 3d305d5..4255634 100644
--- a/apps/web/src/app/api/collections/[id]/tools/route.ts
+++ b/apps/web/src/app/api/collections/[id]/tools/route.ts
@@ -86,6 +86,7 @@ export async function GET(
id: true,
name: true,
description: true,
+ parameters: true,
package: {
select: {
npmPackageName: true,
diff --git a/apps/web/src/components/agents/ChatToolsPanel.tsx b/apps/web/src/components/agents/ChatToolsPanel.tsx
index f7da602..2d9c76d 100644
--- a/apps/web/src/components/agents/ChatToolsPanel.tsx
+++ b/apps/web/src/components/agents/ChatToolsPanel.tsx
@@ -4,6 +4,14 @@ import { Button } from '@tpmjs/ui/Button/Button';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import { useCallback, useState } from 'react';
+export interface ToolParameter {
+ name: string;
+ type: string;
+ description: string;
+ required: boolean;
+ default?: unknown;
+}
+
export interface ToolInfo {
id: string;
toolId: string;
@@ -11,6 +19,7 @@ export interface ToolInfo {
id: string;
name: string;
description: string | null;
+ parameters: ToolParameter[] | null;
package: {
npmPackageName: string;
category: string;
diff --git a/apps/web/src/components/agents/ToolDetailsModal.tsx b/apps/web/src/components/agents/ToolDetailsModal.tsx
index f6fc6ba..9895059 100644
--- a/apps/web/src/components/agents/ToolDetailsModal.tsx
+++ b/apps/web/src/components/agents/ToolDetailsModal.tsx
@@ -18,6 +18,9 @@ export function ToolDetailsModal({ tool, open, onClose }: ToolDetailsModalProps)
if (!tool) return null;
const toolPageUrl = `/tool/${tool.tool.package.npmPackageName}/${tool.tool.name}`;
+ const parameters = tool.tool.parameters || [];
+ const requiredParams = parameters.filter((p) => p.required);
+ const optionalParams = parameters.filter((p) => !p.required);
return (
@@ -58,19 +61,60 @@ export function ToolDetailsModal({ tool, open, onClose }: ToolDetailsModalProps)
)}
{tool.tool.package.category}
+ {parameters.length > 0 && (
+
+ {parameters.length} param{parameters.length !== 1 ? 's' : ''}
+
+ )}
- {/* note about parameters */}
+ {/* parameters fieldset */}
@@ -94,3 +138,37 @@ export function ToolDetailsModal({ tool, open, onClose }: ToolDetailsModalProps)
);
}
+
+interface ParameterRowProps {
+ param: {
+ name: string;
+ type: string;
+ description: string;
+ required: boolean;
+ default?: unknown;
+ };
+}
+
+function ParameterRow({ param }: ParameterRowProps) {
+ return (
+
+
+ {param.name}
+
+ {param.type}
+
+
+ {param.description && (
+
{param.description}
+ )}
+ {param.default !== undefined && (
+
+ default:
+
+ {JSON.stringify(param.default)}
+
+
+ )}
+
+ );
+}
diff --git a/packages/tools/official/blocks.yml b/packages/tools/official/blocks.yml
index 1dde4ed..76a32f4 100644
--- a/packages/tools/official/blocks.yml
+++ b/packages/tools/official/blocks.yml
@@ -5263,7 +5263,1044 @@ blocks:
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance, readme_documentation]
# ---------------------------------------------------------------------------
- # Q) exe.dev VM Management (15 tools)
+ # N) Languages (2 tools)
+ # ---------------------------------------------------------------------------
+ unsandbox.getLanguages:
+ type: utility
+ description: "Get list of all supported programming languages with metadata including extensions, version, and capabilities."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API GET /languages endpoint"
+ inputs: []
+ outputs:
+ - name: languages
+ type: Language[]
+ description: "Array of supported languages with id, name, version, extensions, and capabilities"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.getShells:
+ type: utility
+ description: "Get list of available shell interpreters (bash, zsh, etc.) with their versions and capabilities."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API GET /shells endpoint"
+ inputs: []
+ outputs:
+ - name: shells
+ type: Shell[]
+ description: "Array of available shells with id, name, version, and path"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ # ---------------------------------------------------------------------------
+ # O) Sessions (11 tools)
+ # ---------------------------------------------------------------------------
+ unsandbox.createSession:
+ type: utility
+ description: "Create a new persistent session with configurable language, resources, and networking. Sessions maintain state across executions."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /sessions endpoint"
+ inputs:
+ - name: language
+ type: string
+ description: "Programming language for the session"
+ - name: network_mode
+ type: "'zerotrust' | 'semitrusted'"
+ optional: true
+ description: "Network isolation mode. Default: 'zerotrust'"
+ - name: vcpu
+ type: number
+ optional: true
+ description: "Number of virtual CPUs (0.25-8). Default: 1"
+ - name: memory
+ type: number
+ optional: true
+ description: "Memory in MB (256-8192). Default: 512"
+ - name: ttl
+ type: number
+ optional: true
+ description: "Session timeout in seconds. Default: 3600"
+ - name: env
+ type: "Record"
+ optional: true
+ description: "Environment variables for the session"
+ outputs:
+ - name: session_id
+ type: string
+ description: "Unique session identifier"
+ - name: status
+ type: string
+ description: "Session status (creating, active, frozen, etc.)"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.getSession:
+ type: utility
+ description: "Get details of a session including status, resource usage, and configuration."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API GET /sessions/{id} endpoint"
+ inputs:
+ - name: session_id
+ type: string
+ description: "The session ID to retrieve"
+ outputs:
+ - name: session
+ type: Session
+ description: "Session details including id, status, language, resources, and created_at"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.listSessions:
+ type: utility
+ description: "List all sessions with optional filtering by status (active, frozen, locked)."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API GET /sessions endpoint"
+ inputs:
+ - name: status
+ type: string
+ optional: true
+ description: "Filter by status: active, frozen, locked"
+ outputs:
+ - name: sessions
+ type: Session[]
+ description: "Array of sessions matching the filter"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.executeInSession:
+ type: utility
+ description: "Execute code in an existing session. State and files persist between executions."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /sessions/{id}/execute endpoint"
+ inputs:
+ - name: session_id
+ type: string
+ description: "The session ID to execute in"
+ - name: code
+ type: string
+ description: "Code to execute"
+ - name: timeout
+ type: number
+ optional: true
+ description: "Execution timeout in seconds"
+ outputs:
+ - name: stdout
+ type: string
+ description: "Standard output from execution"
+ - name: stderr
+ type: string
+ description: "Standard error from execution"
+ - name: exit_code
+ type: number
+ description: "Process exit code"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.freezeSession:
+ type: utility
+ description: "Freeze a session to pause execution and reduce resource usage while preserving state."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /sessions/{id}/freeze endpoint"
+ inputs:
+ - name: session_id
+ type: string
+ description: "The session ID to freeze"
+ outputs:
+ - name: success
+ type: boolean
+ description: "Whether the freeze operation succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.unfreezeSession:
+ type: utility
+ description: "Unfreeze a frozen session to resume execution."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /sessions/{id}/unfreeze endpoint"
+ inputs:
+ - name: session_id
+ type: string
+ description: "The session ID to unfreeze"
+ outputs:
+ - name: success
+ type: boolean
+ description: "Whether the unfreeze operation succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.lockSession:
+ type: utility
+ description: "Lock a session to prevent modifications or deletion."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /sessions/{id}/lock endpoint"
+ inputs:
+ - name: session_id
+ type: string
+ description: "The session ID to lock"
+ outputs:
+ - name: success
+ type: boolean
+ description: "Whether the lock operation succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.unlockSession:
+ type: utility
+ description: "Unlock a locked session to allow modifications."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /sessions/{id}/unlock endpoint"
+ inputs:
+ - name: session_id
+ type: string
+ description: "The session ID to unlock"
+ outputs:
+ - name: success
+ type: boolean
+ description: "Whether the unlock operation succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.createSessionSnapshot:
+ type: utility
+ description: "Create a snapshot of the current session state for backup or cloning."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /sessions/{id}/snapshot endpoint"
+ inputs:
+ - name: session_id
+ type: string
+ description: "The session ID to snapshot"
+ - name: name
+ type: string
+ optional: true
+ description: "Name for the snapshot"
+ outputs:
+ - name: snapshot_id
+ type: string
+ description: "ID of the created snapshot"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.restoreSession:
+ type: utility
+ description: "Restore a session from a snapshot."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /sessions/{id}/restore endpoint"
+ inputs:
+ - name: session_id
+ type: string
+ description: "The session ID to restore"
+ - name: snapshot_id
+ type: string
+ description: "The snapshot ID to restore from"
+ outputs:
+ - name: success
+ type: boolean
+ description: "Whether the restore operation succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.deleteSession:
+ type: utility
+ description: "Delete a session and release all associated resources."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API DELETE /sessions/{id} endpoint"
+ inputs:
+ - name: session_id
+ type: string
+ description: "The session ID to delete"
+ outputs:
+ - name: deleted
+ type: boolean
+ description: "Whether the deletion succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ # ---------------------------------------------------------------------------
+ # P) Services (15 tools)
+ # ---------------------------------------------------------------------------
+ unsandbox.createService:
+ type: utility
+ description: "Create a long-running service with persistent state, networking, and auto-restart capabilities."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /services endpoint"
+ inputs:
+ - name: language
+ type: string
+ description: "Programming language for the service"
+ - name: code
+ type: string
+ description: "Service code to run"
+ - name: network_mode
+ type: "'zerotrust' | 'semitrusted'"
+ optional: true
+ description: "Network isolation mode"
+ - name: port
+ type: number
+ optional: true
+ description: "Port to expose"
+ - name: env
+ type: "Record"
+ optional: true
+ description: "Environment variables"
+ outputs:
+ - name: service_id
+ type: string
+ description: "Unique service identifier"
+ - name: url
+ type: string
+ description: "Public URL for the service"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.getService:
+ type: utility
+ description: "Get details of a service including status, endpoints, and resource usage."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API GET /services/{id} endpoint"
+ inputs:
+ - name: service_id
+ type: string
+ description: "The service ID to retrieve"
+ outputs:
+ - name: service
+ type: Service
+ description: "Service details including id, status, url, and resources"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.listServices:
+ type: utility
+ description: "List all services with optional filtering by status."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API GET /services endpoint"
+ inputs:
+ - name: status
+ type: string
+ optional: true
+ description: "Filter by status"
+ outputs:
+ - name: services
+ type: Service[]
+ description: "Array of services"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.executeInService:
+ type: utility
+ description: "Execute a command or code snippet in a running service."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /services/{id}/execute endpoint"
+ inputs:
+ - name: service_id
+ type: string
+ description: "The service ID to execute in"
+ - name: code
+ type: string
+ description: "Code to execute"
+ outputs:
+ - name: stdout
+ type: string
+ description: "Standard output"
+ - name: stderr
+ type: string
+ description: "Standard error"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.freezeService:
+ type: utility
+ description: "Freeze a service to pause execution while preserving state."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /services/{id}/freeze endpoint"
+ inputs:
+ - name: service_id
+ type: string
+ description: "The service ID to freeze"
+ outputs:
+ - name: success
+ type: boolean
+ description: "Whether the freeze operation succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.unfreezeService:
+ type: utility
+ description: "Unfreeze a frozen service to resume execution."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /services/{id}/unfreeze endpoint"
+ inputs:
+ - name: service_id
+ type: string
+ description: "The service ID to unfreeze"
+ outputs:
+ - name: success
+ type: boolean
+ description: "Whether the unfreeze operation succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.lockService:
+ type: utility
+ description: "Lock a service to prevent modifications or deletion."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /services/{id}/lock endpoint"
+ inputs:
+ - name: service_id
+ type: string
+ description: "The service ID to lock"
+ outputs:
+ - name: success
+ type: boolean
+ description: "Whether the lock operation succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.unlockService:
+ type: utility
+ description: "Unlock a locked service to allow modifications."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /services/{id}/unlock endpoint"
+ inputs:
+ - name: service_id
+ type: string
+ description: "The service ID to unlock"
+ outputs:
+ - name: success
+ type: boolean
+ description: "Whether the unlock operation succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.redeployService:
+ type: utility
+ description: "Redeploy a service with updated configuration or code."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /services/{id}/redeploy endpoint"
+ inputs:
+ - name: service_id
+ type: string
+ description: "The service ID to redeploy"
+ - name: code
+ type: string
+ optional: true
+ description: "New code to deploy"
+ outputs:
+ - name: success
+ type: boolean
+ description: "Whether the redeploy operation succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.getServiceLogs:
+ type: utility
+ description: "Get logs from a service with optional filtering by time range and log level."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API GET /services/{id}/logs endpoint"
+ inputs:
+ - name: service_id
+ type: string
+ description: "The service ID to get logs from"
+ - name: since
+ type: string
+ optional: true
+ description: "ISO timestamp to start from"
+ - name: until
+ type: string
+ optional: true
+ description: "ISO timestamp to end at"
+ - name: limit
+ type: number
+ optional: true
+ description: "Maximum number of log entries"
+ outputs:
+ - name: logs
+ type: LogEntry[]
+ description: "Array of log entries"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.createServiceSnapshot:
+ type: utility
+ description: "Create a snapshot of the current service state."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /services/{id}/snapshot endpoint"
+ inputs:
+ - name: service_id
+ type: string
+ description: "The service ID to snapshot"
+ - name: name
+ type: string
+ optional: true
+ description: "Name for the snapshot"
+ outputs:
+ - name: snapshot_id
+ type: string
+ description: "ID of the created snapshot"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.getServiceEnv:
+ type: utility
+ description: "Get environment variables configured for a service."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API GET /services/{id}/env endpoint"
+ inputs:
+ - name: service_id
+ type: string
+ description: "The service ID"
+ outputs:
+ - name: env
+ type: "Record"
+ description: "Environment variables"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.setServiceEnv:
+ type: utility
+ description: "Set or update environment variables for a service."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API PUT /services/{id}/env endpoint"
+ inputs:
+ - name: service_id
+ type: string
+ description: "The service ID"
+ - name: env
+ type: "Record"
+ description: "Environment variables to set"
+ outputs:
+ - name: success
+ type: boolean
+ description: "Whether the operation succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.deleteServiceEnv:
+ type: utility
+ description: "Delete an environment variable from a service."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API DELETE /services/{id}/env/{key} endpoint"
+ inputs:
+ - name: service_id
+ type: string
+ description: "The service ID"
+ - name: key
+ type: string
+ description: "Environment variable key to delete"
+ outputs:
+ - name: success
+ type: boolean
+ description: "Whether the deletion succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.deleteService:
+ type: utility
+ description: "Delete a service and release all associated resources."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API DELETE /services/{id} endpoint"
+ inputs:
+ - name: service_id
+ type: string
+ description: "The service ID to delete"
+ outputs:
+ - name: deleted
+ type: boolean
+ description: "Whether the deletion succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ # ---------------------------------------------------------------------------
+ # Q) Snapshots (8 tools)
+ # ---------------------------------------------------------------------------
+ unsandbox.createSnapshot:
+ type: utility
+ description: "Create a snapshot from any source (session, service, or existing snapshot)."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /snapshots endpoint"
+ inputs:
+ - name: source_type
+ type: "'session' | 'service' | 'snapshot'"
+ description: "Type of source to snapshot"
+ - name: source_id
+ type: string
+ description: "ID of the source"
+ - name: name
+ type: string
+ optional: true
+ description: "Name for the snapshot"
+ outputs:
+ - name: snapshot_id
+ type: string
+ description: "ID of the created snapshot"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.getSnapshot:
+ type: utility
+ description: "Get details of a snapshot including metadata and creation info."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API GET /snapshots/{id} endpoint"
+ inputs:
+ - name: snapshot_id
+ type: string
+ description: "The snapshot ID to retrieve"
+ outputs:
+ - name: snapshot
+ type: Snapshot
+ description: "Snapshot details"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.listSnapshots:
+ type: utility
+ description: "List all snapshots with optional filtering by source type."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API GET /snapshots endpoint"
+ inputs:
+ - name: source_type
+ type: string
+ optional: true
+ description: "Filter by source type"
+ outputs:
+ - name: snapshots
+ type: Snapshot[]
+ description: "Array of snapshots"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.lockSnapshot:
+ type: utility
+ description: "Lock a snapshot to prevent deletion or modification."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /snapshots/{id}/lock endpoint"
+ inputs:
+ - name: snapshot_id
+ type: string
+ description: "The snapshot ID to lock"
+ outputs:
+ - name: success
+ type: boolean
+ description: "Whether the lock operation succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.unlockSnapshot:
+ type: utility
+ description: "Unlock a locked snapshot to allow modifications."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /snapshots/{id}/unlock endpoint"
+ inputs:
+ - name: snapshot_id
+ type: string
+ description: "The snapshot ID to unlock"
+ outputs:
+ - name: success
+ type: boolean
+ description: "Whether the unlock operation succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.restoreSnapshot:
+ type: utility
+ description: "Restore a session or service from a snapshot."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /snapshots/{id}/restore endpoint"
+ inputs:
+ - name: snapshot_id
+ type: string
+ description: "The snapshot ID to restore"
+ - name: target_type
+ type: "'session' | 'service'"
+ description: "Type of resource to create"
+ outputs:
+ - name: id
+ type: string
+ description: "ID of the restored resource"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.cloneSnapshot:
+ type: utility
+ description: "Clone a snapshot to create a new independent copy."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /snapshots/{id}/clone endpoint"
+ inputs:
+ - name: snapshot_id
+ type: string
+ description: "The snapshot ID to clone"
+ - name: name
+ type: string
+ optional: true
+ description: "Name for the cloned snapshot"
+ outputs:
+ - name: snapshot_id
+ type: string
+ description: "ID of the cloned snapshot"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.deleteSnapshot:
+ type: utility
+ description: "Delete a snapshot and free associated storage."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API DELETE /snapshots/{id} endpoint"
+ inputs:
+ - name: snapshot_id
+ type: string
+ description: "The snapshot ID to delete"
+ outputs:
+ - name: deleted
+ type: boolean
+ description: "Whether the deletion succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ # ---------------------------------------------------------------------------
+ # R) Images (12 tools)
+ # ---------------------------------------------------------------------------
+ unsandbox.publishImage:
+ type: utility
+ description: "Publish a snapshot as a reusable image for spawning new sessions or services."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /images endpoint"
+ inputs:
+ - name: snapshot_id
+ type: string
+ description: "The snapshot ID to publish"
+ - name: name
+ type: string
+ description: "Name for the image"
+ - name: visibility
+ type: "'public' | 'private'"
+ optional: true
+ description: "Image visibility. Default: 'private'"
+ outputs:
+ - name: image_id
+ type: string
+ description: "ID of the published image"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.getImage:
+ type: utility
+ description: "Get details of a published image including metadata and access info."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API GET /images/{id} endpoint"
+ inputs:
+ - name: image_id
+ type: string
+ description: "The image ID to retrieve"
+ outputs:
+ - name: image
+ type: Image
+ description: "Image details"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.listImages:
+ type: utility
+ description: "List all images with optional filtering by visibility and ownership."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API GET /images endpoint"
+ inputs:
+ - name: visibility
+ type: string
+ optional: true
+ description: "Filter by visibility: public, private"
+ - name: owned
+ type: boolean
+ optional: true
+ description: "Filter to only owned images"
+ outputs:
+ - name: images
+ type: Image[]
+ description: "Array of images"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.lockImage:
+ type: utility
+ description: "Lock an image to prevent modifications or deletion."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /images/{id}/lock endpoint"
+ inputs:
+ - name: image_id
+ type: string
+ description: "The image ID to lock"
+ outputs:
+ - name: success
+ type: boolean
+ description: "Whether the lock operation succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.unlockImage:
+ type: utility
+ description: "Unlock a locked image to allow modifications."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /images/{id}/unlock endpoint"
+ inputs:
+ - name: image_id
+ type: string
+ description: "The image ID to unlock"
+ outputs:
+ - name: success
+ type: boolean
+ description: "Whether the unlock operation succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.grantImageAccess:
+ type: utility
+ description: "Grant access to a private image for specific users or API keys."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /images/{id}/access endpoint"
+ inputs:
+ - name: image_id
+ type: string
+ description: "The image ID"
+ - name: public_key
+ type: string
+ description: "Public key to grant access to"
+ outputs:
+ - name: success
+ type: boolean
+ description: "Whether the grant operation succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.revokeImageAccess:
+ type: utility
+ description: "Revoke access to an image from specific users or API keys."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API DELETE /images/{id}/access/{key} endpoint"
+ inputs:
+ - name: image_id
+ type: string
+ description: "The image ID"
+ - name: public_key
+ type: string
+ description: "Public key to revoke access from"
+ outputs:
+ - name: success
+ type: boolean
+ description: "Whether the revoke operation succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.transferImage:
+ type: utility
+ description: "Transfer ownership of an image to another user."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /images/{id}/transfer endpoint"
+ inputs:
+ - name: image_id
+ type: string
+ description: "The image ID to transfer"
+ - name: new_owner_key
+ type: string
+ description: "Public key of the new owner"
+ outputs:
+ - name: success
+ type: boolean
+ description: "Whether the transfer succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.setImageVisibility:
+ type: utility
+ description: "Set image visibility to public or private."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API PUT /images/{id}/visibility endpoint"
+ inputs:
+ - name: image_id
+ type: string
+ description: "The image ID"
+ - name: visibility
+ type: "'public' | 'private'"
+ description: "New visibility setting"
+ outputs:
+ - name: success
+ type: boolean
+ description: "Whether the operation succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.spawnFromImage:
+ type: utility
+ description: "Spawn a new session or service from an image."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API POST /images/{id}/spawn endpoint"
+ inputs:
+ - name: image_id
+ type: string
+ description: "The image ID to spawn from"
+ - name: type
+ type: "'session' | 'service'"
+ description: "Type of resource to create"
+ - name: network_mode
+ type: "'zerotrust' | 'semitrusted'"
+ optional: true
+ description: "Network isolation mode"
+ outputs:
+ - name: id
+ type: string
+ description: "ID of the spawned resource"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.getImageTrustedKeys:
+ type: utility
+ description: "Get list of API keys that have access to a private image."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API GET /images/{id}/trusted endpoint"
+ inputs:
+ - name: image_id
+ type: string
+ description: "The image ID"
+ outputs:
+ - name: keys
+ type: string[]
+ description: "Array of public keys with access"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.deleteImage:
+ type: utility
+ description: "Delete an image and free associated storage."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API DELETE /images/{id} endpoint"
+ inputs:
+ - name: image_id
+ type: string
+ description: "The image ID to delete"
+ outputs:
+ - name: deleted
+ type: boolean
+ description: "Whether the deletion succeeded"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ # ---------------------------------------------------------------------------
+ # S) System (4 tools)
+ # ---------------------------------------------------------------------------
+ unsandbox.healthCheck:
+ type: utility
+ description: "Check the health status of the Unsandbox API service."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API GET /health endpoint"
+ inputs: []
+ outputs:
+ - name: status
+ type: string
+ description: "Health status (healthy, degraded, unhealthy)"
+ - name: version
+ type: string
+ description: "API version"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.getClusterStatus:
+ type: utility
+ description: "Get status of the execution cluster including node availability and capacity."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API GET /cluster endpoint"
+ inputs: []
+ outputs:
+ - name: nodes
+ type: number
+ description: "Number of active nodes"
+ - name: capacity
+ type: object
+ description: "Available capacity information"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.getSystemStats:
+ type: utility
+ description: "Get system statistics including resource utilization and job metrics."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API GET /stats endpoint"
+ inputs: []
+ outputs:
+ - name: jobs_total
+ type: number
+ description: "Total jobs processed"
+ - name: jobs_active
+ type: number
+ description: "Currently active jobs"
+ - name: resource_usage
+ type: object
+ description: "Resource utilization metrics"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ unsandbox.listPools:
+ type: utility
+ description: "List available execution pools with their configurations and current status."
+ path: "unsandbox"
+ domain_rules:
+ - id: api_integration
+ description: "Must call Unsandbox API GET /pools endpoint"
+ inputs: []
+ outputs:
+ - name: pools
+ type: Pool[]
+ description: "Array of execution pools"
+ measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
+
+ # ---------------------------------------------------------------------------
+ # T) exe.dev VM Management (15 tools)
# ---------------------------------------------------------------------------
exe.list:
type: utility
diff --git a/packages/tools/official/unsandbox/block.ts b/packages/tools/official/unsandbox/block.ts
index 83fa8c3..d2ba33c 100644
--- a/packages/tools/official/unsandbox/block.ts
+++ b/packages/tools/official/unsandbox/block.ts
@@ -3,26 +3,146 @@
* This file provides metadata for the blocks validator
*/
import {
- deleteJob,
- execute,
+ // Execution tools
executeCodeAsync,
- getJob,
- listJobs,
+ execute,
run,
runAsync,
+ // Job tools
+ getJob,
+ listJobs,
+ deleteJob,
+ // Languages tools
+ getLanguages,
+ getShells,
+ // Session tools
+ createSession,
+ getSession,
+ listSessions,
+ executeInSession,
+ freezeSession,
+ unfreezeSession,
+ lockSession,
+ unlockSession,
+ createSessionSnapshot,
+ restoreSession,
+ deleteSession,
+ // Service tools
+ createService,
+ getService,
+ listServices,
+ executeInService,
+ freezeService,
+ unfreezeService,
+ lockService,
+ unlockService,
+ redeployService,
+ getServiceLogs,
+ createServiceSnapshot,
+ getServiceEnv,
+ setServiceEnv,
+ deleteServiceEnv,
+ deleteService,
+ // Snapshot tools
+ createSnapshot,
+ getSnapshot,
+ listSnapshots,
+ lockSnapshot,
+ unlockSnapshot,
+ restoreSnapshot,
+ cloneSnapshot,
+ deleteSnapshot,
+ // Image tools
+ publishImage,
+ getImage,
+ listImages,
+ lockImage,
+ unlockImage,
+ grantImageAccess,
+ revokeImageAccess,
+ transferImage,
+ setImageVisibility,
+ spawnFromImage,
+ getImageTrustedKeys,
+ deleteImage,
+ // System tools
+ healthCheck,
+ getClusterStatus,
+ getSystemStats,
+ listPools,
} from './src/index.js';
export const block = {
name: 'unsandbox',
description: 'Execute code in a secure sandbox environment supporting 42+ languages',
tools: {
+ // Execution tools
executeCodeAsync,
- getJob,
execute,
run,
runAsync,
+ // Job tools
+ getJob,
listJobs,
deleteJob,
+ // Languages tools
+ getLanguages,
+ getShells,
+ // Session tools
+ createSession,
+ getSession,
+ listSessions,
+ executeInSession,
+ freezeSession,
+ unfreezeSession,
+ lockSession,
+ unlockSession,
+ createSessionSnapshot,
+ restoreSession,
+ deleteSession,
+ // Service tools
+ createService,
+ getService,
+ listServices,
+ executeInService,
+ freezeService,
+ unfreezeService,
+ lockService,
+ unlockService,
+ redeployService,
+ getServiceLogs,
+ createServiceSnapshot,
+ getServiceEnv,
+ setServiceEnv,
+ deleteServiceEnv,
+ deleteService,
+ // Snapshot tools
+ createSnapshot,
+ getSnapshot,
+ listSnapshots,
+ lockSnapshot,
+ unlockSnapshot,
+ restoreSnapshot,
+ cloneSnapshot,
+ deleteSnapshot,
+ // Image tools
+ publishImage,
+ getImage,
+ listImages,
+ lockImage,
+ unlockImage,
+ grantImageAccess,
+ revokeImageAccess,
+ transferImage,
+ setImageVisibility,
+ spawnFromImage,
+ getImageTrustedKeys,
+ deleteImage,
+ // System tools
+ healthCheck,
+ getClusterStatus,
+ getSystemStats,
+ listPools,
},
};
diff --git a/packages/tools/official/unsandbox/package.json b/packages/tools/official/unsandbox/package.json
index 7d9197f..ba9b58c 100644
--- a/packages/tools/official/unsandbox/package.json
+++ b/packages/tools/official/unsandbox/package.json
@@ -1,6 +1,6 @@
{
"name": "@tpmjs/tools-unsandbox",
- "version": "0.1.1",
+ "version": "0.1.2",
"description": "Execute code in a secure sandbox environment. Supports 42+ programming languages with async execution, input files, and compiled artifacts.",
"type": "module",
"keywords": [
@@ -50,10 +50,6 @@
"name": "executeCodeAsync",
"description": "Execute code asynchronously in a secure sandbox. Returns a job_id immediately. Use getJob to check status and retrieve results. Supports 42+ languages."
},
- {
- "name": "getJob",
- "description": "Get the status and results of an async code execution job by job_id."
- },
{
"name": "execute",
"description": "Execute code synchronously in a secure sandbox. Waits for completion and returns results directly. Best for quick scripts."
@@ -66,6 +62,10 @@
"name": "runAsync",
"description": "Execute code asynchronously with automatic language detection via shebang. Returns job_id for polling."
},
+ {
+ "name": "getJob",
+ "description": "Get the status and results of an async code execution job by job_id."
+ },
{
"name": "listJobs",
"description": "List all active code execution jobs with their current status."
@@ -73,6 +73,214 @@
{
"name": "deleteJob",
"description": "Cancel an active code execution job by job_id."
+ },
+ {
+ "name": "getLanguages",
+ "description": "Get list of all supported programming languages with their metadata including extensions, version, and capabilities."
+ },
+ {
+ "name": "getShells",
+ "description": "Get list of available shell interpreters (bash, zsh, etc.) with their versions and capabilities."
+ },
+ {
+ "name": "createSession",
+ "description": "Create a new persistent session with configurable language, resources, and networking. Sessions maintain state across executions."
+ },
+ {
+ "name": "getSession",
+ "description": "Get details of a session including status, resource usage, and configuration."
+ },
+ {
+ "name": "listSessions",
+ "description": "List all sessions with optional filtering by status (active, frozen, locked)."
+ },
+ {
+ "name": "executeInSession",
+ "description": "Execute code in an existing session. State and files persist between executions."
+ },
+ {
+ "name": "freezeSession",
+ "description": "Freeze a session to pause execution and reduce resource usage while preserving state."
+ },
+ {
+ "name": "unfreezeSession",
+ "description": "Unfreeze a frozen session to resume execution."
+ },
+ {
+ "name": "lockSession",
+ "description": "Lock a session to prevent modifications or deletion."
+ },
+ {
+ "name": "unlockSession",
+ "description": "Unlock a locked session to allow modifications."
+ },
+ {
+ "name": "createSessionSnapshot",
+ "description": "Create a snapshot of the current session state for backup or cloning."
+ },
+ {
+ "name": "restoreSession",
+ "description": "Restore a session from a snapshot."
+ },
+ {
+ "name": "deleteSession",
+ "description": "Delete a session and release all associated resources."
+ },
+ {
+ "name": "createService",
+ "description": "Create a long-running service with persistent state, networking, and auto-restart capabilities."
+ },
+ {
+ "name": "getService",
+ "description": "Get details of a service including status, endpoints, and resource usage."
+ },
+ {
+ "name": "listServices",
+ "description": "List all services with optional filtering by status."
+ },
+ {
+ "name": "executeInService",
+ "description": "Execute a command or code snippet in a running service."
+ },
+ {
+ "name": "freezeService",
+ "description": "Freeze a service to pause execution while preserving state."
+ },
+ {
+ "name": "unfreezeService",
+ "description": "Unfreeze a frozen service to resume execution."
+ },
+ {
+ "name": "lockService",
+ "description": "Lock a service to prevent modifications or deletion."
+ },
+ {
+ "name": "unlockService",
+ "description": "Unlock a locked service to allow modifications."
+ },
+ {
+ "name": "redeployService",
+ "description": "Redeploy a service with updated configuration or code."
+ },
+ {
+ "name": "getServiceLogs",
+ "description": "Get logs from a service with optional filtering by time range and log level."
+ },
+ {
+ "name": "createServiceSnapshot",
+ "description": "Create a snapshot of the current service state."
+ },
+ {
+ "name": "getServiceEnv",
+ "description": "Get environment variables configured for a service."
+ },
+ {
+ "name": "setServiceEnv",
+ "description": "Set or update environment variables for a service."
+ },
+ {
+ "name": "deleteServiceEnv",
+ "description": "Delete an environment variable from a service."
+ },
+ {
+ "name": "deleteService",
+ "description": "Delete a service and release all associated resources."
+ },
+ {
+ "name": "createSnapshot",
+ "description": "Create a snapshot from any source (session, service, or existing snapshot)."
+ },
+ {
+ "name": "getSnapshot",
+ "description": "Get details of a snapshot including metadata and creation info."
+ },
+ {
+ "name": "listSnapshots",
+ "description": "List all snapshots with optional filtering by source type."
+ },
+ {
+ "name": "lockSnapshot",
+ "description": "Lock a snapshot to prevent deletion or modification."
+ },
+ {
+ "name": "unlockSnapshot",
+ "description": "Unlock a locked snapshot to allow modifications."
+ },
+ {
+ "name": "restoreSnapshot",
+ "description": "Restore a session or service from a snapshot."
+ },
+ {
+ "name": "cloneSnapshot",
+ "description": "Clone a snapshot to create a new independent copy."
+ },
+ {
+ "name": "deleteSnapshot",
+ "description": "Delete a snapshot and free associated storage."
+ },
+ {
+ "name": "publishImage",
+ "description": "Publish a snapshot as a reusable image for spawning new sessions or services."
+ },
+ {
+ "name": "getImage",
+ "description": "Get details of a published image including metadata and access info."
+ },
+ {
+ "name": "listImages",
+ "description": "List all images with optional filtering by visibility and ownership."
+ },
+ {
+ "name": "lockImage",
+ "description": "Lock an image to prevent modifications or deletion."
+ },
+ {
+ "name": "unlockImage",
+ "description": "Unlock a locked image to allow modifications."
+ },
+ {
+ "name": "grantImageAccess",
+ "description": "Grant access to a private image for specific users or API keys."
+ },
+ {
+ "name": "revokeImageAccess",
+ "description": "Revoke access to an image from specific users or API keys."
+ },
+ {
+ "name": "transferImage",
+ "description": "Transfer ownership of an image to another user."
+ },
+ {
+ "name": "setImageVisibility",
+ "description": "Set image visibility to public or private."
+ },
+ {
+ "name": "spawnFromImage",
+ "description": "Spawn a new session or service from an image."
+ },
+ {
+ "name": "getImageTrustedKeys",
+ "description": "Get list of API keys that have access to a private image."
+ },
+ {
+ "name": "deleteImage",
+ "description": "Delete an image and free associated storage."
+ },
+ {
+ "name": "healthCheck",
+ "description": "Check the health status of the Unsandbox API service."
+ },
+ {
+ "name": "getClusterStatus",
+ "description": "Get status of the execution cluster including node availability and capacity."
+ },
+ {
+ "name": "getSystemStats",
+ "description": "Get system statistics including resource utilization and job metrics."
+ },
+ {
+ "name": "listPools",
+ "description": "List available execution pools with their configurations and current status."
}
]
},
diff --git a/packages/tools/official/unsandbox/src/index.ts b/packages/tools/official/unsandbox/src/index.ts
index ebedc22..d880761 100644
--- a/packages/tools/official/unsandbox/src/index.ts
+++ b/packages/tools/official/unsandbox/src/index.ts
@@ -2,9 +2,11 @@
* Unsandbox Code Execution Tools for TPMJS
* Execute code in a secure sandbox environment supporting 42+ languages.
*
- * @requires UNSANDBOX_API_KEY environment variable
+ * @requires UNSANDBOX_PUBLIC_KEY environment variable (unsb-pk-xxxx-xxxx-xxxx-xxxx)
+ * @requires UNSANDBOX_SECRET_KEY environment variable (unsb-sk-xxxxx-xxxxx-xxxxx-xxxxx)
*/
+import { createHmac } from 'node:crypto';
import { jsonSchema, tool } from 'ai';
const UNSANDBOX_API_BASE = 'https://api.unsandbox.com';
@@ -61,50 +63,166 @@ export type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number];
export type NetworkMode = 'zerotrust' | 'semitrusted';
+export type JobStatus = 'pending' | 'running' | 'completed' | 'cancelled' | 'timeout' | 'failed';
+
export interface InputFile {
filename: string;
content: string; // Base64 encoded
}
+export interface Artifact {
+ type: 'base64';
+ filename: string;
+ data: string;
+}
+
+/**
+ * Get API keys from environment variables
+ */
+function getApiKeys(): { publicKey: string; secretKey: string } {
+ const publicKey = process.env.UNSANDBOX_PUBLIC_KEY;
+ const secretKey = process.env.UNSANDBOX_SECRET_KEY;
+
+ if (!publicKey) {
+ throw new Error(
+ 'UNSANDBOX_PUBLIC_KEY environment variable is required. Get your API keys from https://unsandbox.com/pricing-for-agents'
+ );
+ }
+
+ if (!secretKey) {
+ throw new Error(
+ 'UNSANDBOX_SECRET_KEY environment variable is required. Get your API keys from https://unsandbox.com/pricing-for-agents'
+ );
+ }
+
+ return { publicKey, secretKey };
+}
+
+/**
+ * Compute HMAC-SHA256 signature for API authentication
+ * Message format: "{timestamp}:{METHOD}:{path}:{body}"
+ */
+function computeSignature(
+ secretKey: string,
+ timestamp: number,
+ method: string,
+ path: string,
+ body: string
+): string {
+ const message = `${timestamp}:${method}:${path}:${body}`;
+ return createHmac('sha256', secretKey).update(message).digest('hex').toLowerCase();
+}
+
+/**
+ * Make an authenticated request to the Unsandbox API
+ */
+async function apiRequest(
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE',
+ path: string,
+ body?: unknown
+): Promise {
+ const { publicKey, secretKey } = getApiKeys();
+ const timestamp = Math.floor(Date.now() / 1000);
+ const bodyString = body !== undefined ? JSON.stringify(body) : '';
+ const signature = computeSignature(secretKey, timestamp, method, path, bodyString);
+
+ const headers: Record = {
+ Authorization: `Bearer ${publicKey}`,
+ 'X-Timestamp': timestamp.toString(),
+ 'X-Signature': signature,
+ };
+
+ if (body !== undefined) {
+ headers['Content-Type'] = 'application/json';
+ }
+
+ const response = await fetch(`${UNSANDBOX_API_BASE}${path}`, {
+ method,
+ headers,
+ body: body !== undefined ? bodyString : undefined,
+ });
+
+ if (!response.ok) {
+ const errorText = await response.text().catch(() => 'Unknown error');
+ handleApiError(response.status, errorText);
+ }
+
+ return response.json() as Promise;
+}
+
+/**
+ * Handle API errors with specific messages for common HTTP status codes
+ */
+function handleApiError(status: number, errorText: string): never {
+ switch (status) {
+ case 400:
+ throw new Error(`Bad request: ${errorText}`);
+ case 401:
+ throw new Error(
+ `Authentication failed: Invalid API keys. Ensure UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY are correct.`
+ );
+ case 403:
+ throw new Error(`Access forbidden: ${errorText}`);
+ case 404:
+ throw new Error(`Resource not found: ${errorText}`);
+ case 429:
+ throw new Error(`Rate limit exceeded or concurrency limit reached: ${errorText}`);
+ case 500:
+ case 502:
+ case 503:
+ throw new Error(`Unsandbox service error (${status}): ${errorText}`);
+ default:
+ throw new Error(`Unsandbox API error: HTTP ${status} - ${errorText}`);
+ }
+}
+
+/**
+ * Make an authenticated request with text/plain body (for /run endpoints)
+ */
+async function apiRequestText(path: string, body: string): Promise {
+ const { publicKey, secretKey } = getApiKeys();
+ const timestamp = Math.floor(Date.now() / 1000);
+ const signature = computeSignature(secretKey, timestamp, 'POST', path, body);
+
+ const response = await fetch(`${UNSANDBOX_API_BASE}${path}`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'text/plain',
+ Authorization: `Bearer ${publicKey}`,
+ 'X-Timestamp': timestamp.toString(),
+ 'X-Signature': signature,
+ },
+ body,
+ });
+
+ if (!response.ok) {
+ const errorText = await response.text().catch(() => 'Unknown error');
+ handleApiError(response.status, errorText);
+ }
+
+ return response.json() as Promise;
+}
+
+// ============================================================================
+// Execute Async
+// ============================================================================
+
export interface ExecuteAsyncInput {
language: string;
code: string;
input_files?: InputFile[];
+ env?: Record;
network_mode?: NetworkMode;
ttl?: number;
+ vcpu?: number;
return_artifact?: boolean;
return_wasm_artifact?: boolean;
}
export interface ExecuteAsyncResult {
job_id: string;
- status: string;
-}
-
-export interface GetJobInput {
- job_id: string;
-}
-
-export interface GetJobResult {
- job_id: string;
- status: 'queued' | 'running' | 'completed' | 'failed';
- stdout?: string;
- stderr?: string;
- exit_code?: number;
- duration_ms?: number;
- error?: string;
- artifact?: string;
- wasm_artifact?: string;
-}
-
-function getApiKey(): string {
- const key = process.env.UNSANDBOX_API_KEY;
- if (!key) {
- throw new Error(
- 'UNSANDBOX_API_KEY environment variable is required. Get your API key from https://unsandbox.com'
- );
- }
- return key;
+ status: 'pending';
+ message: string;
}
/**
@@ -145,6 +263,11 @@ export const executeCodeAsync = tool({
required: ['filename', 'content'],
},
},
+ env: {
+ type: 'object',
+ description: 'Environment variables as key-value pairs.',
+ additionalProperties: { type: 'string' },
+ },
network_mode: {
type: 'string',
enum: ['zerotrust', 'semitrusted'],
@@ -155,6 +278,10 @@ export const executeCodeAsync = tool({
type: 'number',
description: 'Execution timeout in seconds (1-900). Default: 60.',
},
+ vcpu: {
+ type: 'number',
+ description: 'Number of vCPUs (1-8). Each vCPU includes 2GB RAM. Default: 1.',
+ },
return_artifact: {
type: 'boolean',
description: 'For compiled languages, return the compiled binary.',
@@ -168,37 +295,51 @@ export const executeCodeAsync = tool({
additionalProperties: false,
}),
async execute(input: ExecuteAsyncInput): Promise {
- const apiKey = getApiKey();
-
- // Validate language
if (!input.language || typeof input.language !== 'string') {
throw new Error('Language is required and must be a string');
}
+ const normalizedLanguage = input.language.toLowerCase();
+ if (!SUPPORTED_LANGUAGES.includes(normalizedLanguage as SupportedLanguage)) {
+ throw new Error(
+ `Unsupported language: "${input.language}". Supported languages: ${SUPPORTED_LANGUAGES.join(', ')}`
+ );
+ }
+
if (!input.code || typeof input.code !== 'string') {
throw new Error('Code is required and must be a string');
}
- // Validate TTL if provided
- if (input.ttl !== undefined && (input.ttl < 1 || input.ttl > 900)) {
+ // Default TTL to 60 seconds if not provided (API default)
+ const ttl = input.ttl ?? 60;
+ if (ttl < 1 || ttl > 900) {
throw new Error('TTL must be between 1 and 900 seconds');
}
+ if (input.vcpu !== undefined && (input.vcpu < 1 || input.vcpu > 8)) {
+ throw new Error('vCPU must be between 1 and 8');
+ }
+
+ // Default network_mode to 'zerotrust' (most secure)
+ const networkMode = input.network_mode ?? 'zerotrust';
+
const requestBody: Record = {
- language: input.language.toLowerCase(),
+ language: normalizedLanguage,
code: input.code,
+ network_mode: networkMode,
+ ttl,
};
if (input.input_files) {
requestBody.input_files = input.input_files;
}
- if (input.network_mode) {
- requestBody.network_mode = input.network_mode;
+ if (input.env) {
+ requestBody.env = input.env;
}
- if (input.ttl) {
- requestBody.ttl = input.ttl;
+ if (input.vcpu) {
+ requestBody.vcpu = input.vcpu;
}
if (input.return_artifact) {
@@ -209,36 +350,51 @@ export const executeCodeAsync = tool({
requestBody.return_wasm_artifact = input.return_wasm_artifact;
}
- const response = await fetch(`${UNSANDBOX_API_BASE}/execute/async`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${apiKey}`,
- },
- body: JSON.stringify(requestBody),
- });
-
- if (!response.ok) {
- const errorText = await response.text().catch(() => 'Unknown error');
- throw new Error(`Unsandbox API error: HTTP ${response.status} - ${errorText}`);
- }
-
- const result = (await response.json()) as { job_id: string; status?: string };
+ // Call the Unsandbox API POST /execute/async endpoint
+ const result = await apiRequest('POST', '/execute/async', requestBody);
return {
job_id: result.job_id,
- status: result.status || 'queued',
+ status: 'pending',
+ message: result.message || 'Job accepted for execution',
};
},
});
+// ============================================================================
+// Get Job
+// ============================================================================
+
+export interface GetJobInput {
+ job_id: string;
+}
+
+export interface GetJobResult {
+ job_id: string;
+ status: JobStatus;
+ language?: string;
+ network_mode?: string;
+ stdout?: string;
+ stderr?: string;
+ exit_code?: number;
+ total_time_ms?: number;
+ error?: string;
+ artifacts?: Artifact[];
+ artifact?: Artifact;
+ wasm_artifact?: Artifact;
+ created_at?: string;
+ started_at?: string;
+ completed_at?: string;
+ success?: boolean;
+}
+
/**
* Get the status and results of an async code execution job.
* Poll this endpoint until status is 'completed' or 'failed'.
*/
export const getJob = tool({
description:
- "Get the status and results of an async code execution job. Poll this endpoint until status is 'completed' or 'failed'.",
+ "Get the status and results of an async code execution job. Poll this endpoint until status is 'completed', 'failed', 'cancelled', or 'timeout'.",
inputSchema: jsonSchema({
type: 'object',
properties: {
@@ -251,73 +407,64 @@ export const getJob = tool({
additionalProperties: false,
}),
async execute(input: GetJobInput): Promise {
- const apiKey = getApiKey();
-
if (!input.job_id || typeof input.job_id !== 'string') {
throw new Error('job_id is required and must be a string');
}
- const response = await fetch(`${UNSANDBOX_API_BASE}/jobs/${encodeURIComponent(input.job_id)}`, {
- method: 'GET',
- headers: {
- Authorization: `Bearer ${apiKey}`,
- },
- });
-
- if (!response.ok) {
- if (response.status === 404) {
- throw new Error(`Job not found: ${input.job_id}`);
- }
- const errorText = await response.text().catch(() => 'Unknown error');
- throw new Error(`Unsandbox API error: HTTP ${response.status} - ${errorText}`);
- }
-
- const result = (await response.json()) as GetJobResult;
+ const result = await apiRequest(
+ 'GET',
+ `/jobs/${encodeURIComponent(input.job_id)}`,
+ undefined
+ );
return {
job_id: result.job_id || input.job_id,
status: result.status,
+ language: result.language,
+ network_mode: result.network_mode,
stdout: result.stdout,
stderr: result.stderr,
exit_code: result.exit_code,
- duration_ms: result.duration_ms,
+ total_time_ms: result.total_time_ms,
error: result.error,
+ artifacts: result.artifacts,
artifact: result.artifact,
wasm_artifact: result.wasm_artifact,
+ created_at: result.created_at,
+ started_at: result.started_at,
+ completed_at: result.completed_at,
+ success: result.success,
};
},
});
-/**
- * Sync execution interfaces
- */
+// ============================================================================
+// Execute Sync
+// ============================================================================
+
export interface ExecuteSyncInput {
language: string;
code: string;
input_files?: InputFile[];
+ env?: Record;
network_mode?: NetworkMode;
ttl?: number;
+ vcpu?: number;
+ return_artifact?: boolean;
+ return_wasm_artifact?: boolean;
}
export interface ExecuteSyncResult {
+ job_id: string;
+ language: string;
+ network_mode: string;
stdout: string;
stderr: string;
exit_code: number;
- duration_ms: number;
-}
-
-export interface RunInput {
- code: string;
- network_mode?: NetworkMode;
- ttl?: number;
-}
-
-export interface RunResult {
- stdout: string;
- stderr: string;
- exit_code: number;
- detected_language: string;
- duration_ms: number;
+ total_time_ms: number;
+ success: boolean;
+ artifact?: Artifact;
+ wasm_artifact?: Artifact;
}
/**
@@ -351,6 +498,11 @@ export const execute = tool({
required: ['filename', 'content'],
},
},
+ env: {
+ type: 'object',
+ description: 'Environment variables as key-value pairs.',
+ additionalProperties: { type: 'string' },
+ },
network_mode: {
type: 'string',
enum: ['zerotrust', 'semitrusted'],
@@ -360,80 +512,131 @@ export const execute = tool({
type: 'number',
description: 'Execution timeout in seconds (1-900). Default: 60.',
},
+ vcpu: {
+ type: 'number',
+ description: 'Number of vCPUs (1-8). Each vCPU includes 2GB RAM. Default: 1.',
+ },
+ return_artifact: {
+ type: 'boolean',
+ description: 'For compiled languages, return the compiled binary.',
+ },
+ return_wasm_artifact: {
+ type: 'boolean',
+ description: 'Compile to WebAssembly. Supported for C, C++, Rust, Zig, Go.',
+ },
},
required: ['language', 'code'],
additionalProperties: false,
}),
async execute(input: ExecuteSyncInput): Promise {
- const apiKey = getApiKey();
-
if (!input.language || typeof input.language !== 'string') {
throw new Error('Language is required and must be a string');
}
+ const normalizedLanguage = input.language.toLowerCase();
+ if (!SUPPORTED_LANGUAGES.includes(normalizedLanguage as SupportedLanguage)) {
+ throw new Error(
+ `Unsupported language: "${input.language}". Supported languages: ${SUPPORTED_LANGUAGES.join(', ')}`
+ );
+ }
+
if (!input.code || typeof input.code !== 'string') {
throw new Error('Code is required and must be a string');
}
- if (input.ttl !== undefined && (input.ttl < 1 || input.ttl > 900)) {
+ // Default TTL to 60 seconds if not provided (API default)
+ const ttl = input.ttl ?? 60;
+ if (ttl < 1 || ttl > 900) {
throw new Error('TTL must be between 1 and 900 seconds');
}
+ if (input.vcpu !== undefined && (input.vcpu < 1 || input.vcpu > 8)) {
+ throw new Error('vCPU must be between 1 and 8');
+ }
+
+ // Default network_mode to 'zerotrust' (most secure)
+ const networkMode = input.network_mode ?? 'zerotrust';
+
const requestBody: Record = {
- language: input.language.toLowerCase(),
+ language: normalizedLanguage,
code: input.code,
+ network_mode: networkMode,
+ ttl,
};
if (input.input_files) {
requestBody.input_files = input.input_files;
}
- if (input.network_mode) {
- requestBody.network_mode = input.network_mode;
+ if (input.env) {
+ requestBody.env = input.env;
}
- if (input.ttl) {
- requestBody.ttl = input.ttl;
+ if (input.vcpu) {
+ requestBody.vcpu = input.vcpu;
}
- const response = await fetch(`${UNSANDBOX_API_BASE}/execute`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${apiKey}`,
- },
- body: JSON.stringify(requestBody),
- });
-
- if (!response.ok) {
- const errorText = await response.text().catch(() => 'Unknown error');
- throw new Error(`Unsandbox API error: HTTP ${response.status} - ${errorText}`);
+ if (input.return_artifact) {
+ requestBody.return_artifact = input.return_artifact;
}
- const result = (await response.json()) as ExecuteSyncResult;
+ if (input.return_wasm_artifact) {
+ requestBody.return_wasm_artifact = input.return_wasm_artifact;
+ }
+
+ // Call the Unsandbox API POST /execute endpoint (synchronous)
+ const result = await apiRequest('POST', '/execute', requestBody);
return {
+ job_id: result.job_id,
+ language: result.language,
+ network_mode: result.network_mode || 'zerotrust',
stdout: result.stdout || '',
stderr: result.stderr || '',
exit_code: result.exit_code ?? 0,
- duration_ms: result.duration_ms || 0,
+ total_time_ms: result.total_time_ms || 0,
+ success: result.success ?? (result.exit_code === 0),
+ artifact: result.artifact,
+ wasm_artifact: result.wasm_artifact,
};
},
});
+// ============================================================================
+// Run (auto-detect language)
+// ============================================================================
+
+export interface RunInput {
+ code: string;
+ network_mode?: NetworkMode;
+ ttl?: number;
+ env?: Record;
+}
+
+export interface RunResult {
+ job_id: string;
+ language: string;
+ detected_language: string;
+ stdout: string;
+ stderr: string;
+ exit_code: number;
+ success: boolean;
+}
+
/**
- * Execute code with automatic language detection from shebang.
- * Send raw code with a shebang line (e.g., #!/usr/bin/env python) and the language is auto-detected.
+ * Execute code with automatic language detection.
+ * Uses shebang lines, syntax patterns, and heuristics for detection.
*/
export const run = tool({
description:
- 'Execute code with automatic language detection from shebang. Send raw code with a shebang line (e.g., #!/usr/bin/env python) and the language is auto-detected.',
+ 'Execute code with automatic language detection. Uses shebang lines (e.g., #!/usr/bin/env python), syntax patterns, and heuristics for detection.',
inputSchema: jsonSchema({
type: 'object',
properties: {
code: {
type: 'string',
- description: 'The source code with shebang line (e.g., #!/usr/bin/env python)',
+ description:
+ 'The source code to execute. Can include shebang line (e.g., #!/usr/bin/env python) for explicit language hint.',
},
network_mode: {
type: 'string',
@@ -444,13 +647,16 @@ export const run = tool({
type: 'number',
description: 'Execution timeout in seconds (1-900). Default: 60.',
},
+ env: {
+ type: 'object',
+ description: 'Environment variables as key-value pairs (URL-encoded JSON).',
+ additionalProperties: { type: 'string' },
+ },
},
required: ['code'],
additionalProperties: false,
}),
async execute(input: RunInput): Promise {
- const apiKey = getApiKey();
-
if (!input.code || typeof input.code !== 'string') {
throw new Error('Code is required and must be a string');
}
@@ -459,44 +665,41 @@ export const run = tool({
throw new Error('TTL must be between 1 and 900 seconds');
}
- // Build query params for network_mode and ttl
- const url = new URL(`${UNSANDBOX_API_BASE}/run`);
+ // Build query params
+ let path = '/run';
+ const params = new URLSearchParams();
if (input.network_mode) {
- url.searchParams.set('network_mode', input.network_mode);
+ params.set('network_mode', input.network_mode);
}
if (input.ttl) {
- url.searchParams.set('ttl', input.ttl.toString());
+ params.set('ttl', input.ttl.toString());
+ }
+ if (input.env) {
+ params.set('env', JSON.stringify(input.env));
+ }
+ const queryString = params.toString();
+ if (queryString) {
+ path = `${path}?${queryString}`;
}
- const response = await fetch(url.toString(), {
- method: 'POST',
- headers: {
- 'Content-Type': 'text/plain',
- Authorization: `Bearer ${apiKey}`,
- },
- body: input.code,
- });
-
- if (!response.ok) {
- const errorText = await response.text().catch(() => 'Unknown error');
- throw new Error(`Unsandbox API error: HTTP ${response.status} - ${errorText}`);
- }
-
- const result = (await response.json()) as RunResult;
+ const result = await apiRequestText(path, input.code);
return {
+ job_id: result.job_id,
+ language: result.language || result.detected_language || 'unknown',
+ detected_language: result.detected_language || result.language || 'unknown',
stdout: result.stdout || '',
stderr: result.stderr || '',
exit_code: result.exit_code ?? 0,
- detected_language: result.detected_language || 'unknown',
- duration_ms: result.duration_ms || 0,
+ success: result.success ?? (result.exit_code === 0),
};
},
});
-/**
- * Additional interfaces for job management
- */
+// ============================================================================
+// Run Async (auto-detect language)
+// ============================================================================
+
export interface RunAsyncInput {
code: string;
network_mode?: NetworkMode;
@@ -505,41 +708,25 @@ export interface RunAsyncInput {
export interface RunAsyncResult {
job_id: string;
- status: string;
-}
-
-export interface ListJobsResult {
- jobs: Array<{
- id: string;
- status: string;
- language?: string;
- created_at?: string;
- }>;
- count: number;
-}
-
-export interface DeleteJobInput {
- job_id: string;
-}
-
-export interface DeleteJobResult {
- deleted: boolean;
- job_id: string;
+ status: 'pending';
+ detected_language: string;
+ message: string;
}
/**
- * Execute code asynchronously with automatic language detection from shebang.
+ * Execute code asynchronously with automatic language detection.
* Returns a job_id immediately. Use getJob to check status and retrieve results.
*/
export const runAsync = tool({
description:
- 'Execute code asynchronously with automatic language detection from shebang. Returns a job_id immediately. Use getJob to check status and retrieve results.',
+ 'Execute code asynchronously with automatic language detection. Returns a job_id immediately. Use getJob to check status and retrieve results.',
inputSchema: jsonSchema({
type: 'object',
properties: {
code: {
type: 'string',
- description: 'The source code with shebang line (e.g., #!/usr/bin/env python)',
+ description:
+ 'The source code to execute. Can include shebang line (e.g., #!/usr/bin/env python) for explicit language hint.',
},
network_mode: {
type: 'string',
@@ -555,8 +742,6 @@ export const runAsync = tool({
additionalProperties: false,
}),
async execute(input: RunAsyncInput): Promise {
- const apiKey = getApiKey();
-
if (!input.code || typeof input.code !== 'string') {
throw new Error('Code is required and must be a string');
}
@@ -565,37 +750,48 @@ export const runAsync = tool({
throw new Error('TTL must be between 1 and 900 seconds');
}
- const url = new URL(`${UNSANDBOX_API_BASE}/run/async`);
+ // Build query params
+ let path = '/run/async';
+ const params = new URLSearchParams();
if (input.network_mode) {
- url.searchParams.set('network_mode', input.network_mode);
+ params.set('network_mode', input.network_mode);
}
if (input.ttl) {
- url.searchParams.set('ttl', input.ttl.toString());
+ params.set('ttl', input.ttl.toString());
+ }
+ const queryString = params.toString();
+ if (queryString) {
+ path = `${path}?${queryString}`;
}
- const response = await fetch(url.toString(), {
- method: 'POST',
- headers: {
- 'Content-Type': 'text/plain',
- Authorization: `Bearer ${apiKey}`,
- },
- body: input.code,
- });
-
- if (!response.ok) {
- const errorText = await response.text().catch(() => 'Unknown error');
- throw new Error(`Unsandbox API error: HTTP ${response.status} - ${errorText}`);
- }
-
- const result = (await response.json()) as { job_id: string; status?: string };
+ const result = await apiRequestText(path, input.code);
return {
job_id: result.job_id,
- status: result.status || 'queued',
+ status: 'pending',
+ detected_language: result.detected_language || 'unknown',
+ message: result.message || 'Job accepted for execution',
};
},
});
+// ============================================================================
+// List Jobs
+// ============================================================================
+
+export interface JobSummary {
+ job_id: string;
+ language: string;
+ network_mode: string;
+ status: JobStatus;
+ created_at: string;
+}
+
+export interface ListJobsResult {
+ jobs: JobSummary[];
+ count: number;
+}
+
/**
* List all active (pending or running) code execution jobs.
*/
@@ -608,41 +804,50 @@ export const listJobs = tool({
additionalProperties: false,
}),
async execute(): Promise {
- const apiKey = getApiKey();
+ // API returns array directly per OpenAPI spec
+ const result = await apiRequest('GET', '/jobs', undefined);
- const response = await fetch(`${UNSANDBOX_API_BASE}/jobs`, {
- method: 'GET',
- headers: {
- Authorization: `Bearer ${apiKey}`,
- },
- });
-
- if (!response.ok) {
- const errorText = await response.text().catch(() => 'Unknown error');
- throw new Error(`Unsandbox API error: HTTP ${response.status} - ${errorText}`);
- }
-
- const result = (await response.json()) as { jobs?: Array> };
- const jobs = (result.jobs || []).map((job) => ({
- id: String(job.id || job.job_id || ''),
- status: String(job.status || 'unknown'),
- language: job.language ? String(job.language) : undefined,
- created_at: job.created_at ? String(job.created_at) : undefined,
- }));
+ // Handle both array response and object with jobs array
+ const jobs = Array.isArray(result) ? result : [];
return {
- jobs,
+ jobs: jobs.map((job) => ({
+ job_id: job.job_id || '',
+ language: job.language || 'unknown',
+ network_mode: job.network_mode || 'zerotrust',
+ status: job.status || 'pending',
+ created_at: job.created_at || '',
+ })),
count: jobs.length,
};
},
});
+// ============================================================================
+// Delete/Cancel Job
+// ============================================================================
+
+export interface DeleteJobInput {
+ job_id: string;
+}
+
+export interface DeleteJobResult {
+ job_id: string;
+ status: string;
+ message: string;
+ stdout?: string;
+ stderr?: string;
+ success: boolean;
+ artifacts?: Artifact[];
+}
+
/**
* Cancel a pending or running code execution job.
+ * Returns any partial output and compiled artifacts.
*/
export const deleteJob = tool({
description:
- 'Cancel a pending or running code execution job. The job will be terminated and resources freed.',
+ 'Cancel a pending or running code execution job. Returns any partial output and compiled artifacts. Cancellation is best-effort - jobs may complete before cancellation takes effect.',
inputSchema: jsonSchema({
type: 'object',
properties: {
@@ -655,36 +860,1623 @@ export const deleteJob = tool({
additionalProperties: false,
}),
async execute(input: DeleteJobInput): Promise {
- const apiKey = getApiKey();
-
if (!input.job_id || typeof input.job_id !== 'string') {
throw new Error('job_id is required and must be a string');
}
- const response = await fetch(`${UNSANDBOX_API_BASE}/jobs/${encodeURIComponent(input.job_id)}`, {
- method: 'DELETE',
- headers: {
- Authorization: `Bearer ${apiKey}`,
- },
- });
-
- if (!response.ok) {
- if (response.status === 404) {
- throw new Error(`Job not found: ${input.job_id}`);
- }
- const errorText = await response.text().catch(() => 'Unknown error');
- throw new Error(`Unsandbox API error: HTTP ${response.status} - ${errorText}`);
- }
+ const result = await apiRequest(
+ 'DELETE',
+ `/jobs/${encodeURIComponent(input.job_id)}`,
+ undefined
+ );
return {
- deleted: true,
- job_id: input.job_id,
+ job_id: result.job_id || input.job_id,
+ status: result.status || 'cancelled',
+ message: result.message || 'Job cancelled',
+ stdout: result.stdout,
+ stderr: result.stderr,
+ success: result.success ?? true,
+ artifacts: result.artifacts,
};
},
});
-// Default export for convenience
+// ============================================================================
+// Languages
+// ============================================================================
+
+export interface LanguagesResult {
+ languages: string[];
+ aliases: Record;
+ count: number;
+}
+
+/**
+ * List all supported programming languages.
+ */
+export const getLanguages = tool({
+ description:
+ 'List all supported programming languages and their aliases for code execution.',
+ inputSchema: jsonSchema>({
+ type: 'object',
+ properties: {},
+ additionalProperties: false,
+ }),
+ async execute(): Promise {
+ const result = await apiRequest('GET', '/languages', undefined);
+ return {
+ languages: result.languages || [],
+ aliases: result.aliases || {},
+ count: result.count || result.languages?.length || 0,
+ };
+ },
+});
+
+export interface ShellsResult {
+ shells: string[];
+ categories: Record;
+ count: number;
+}
+
+/**
+ * List all supported shells and REPLs for interactive sessions.
+ */
+export const getShells = tool({
+ description:
+ 'List all supported shells and REPLs for interactive sessions, grouped by category.',
+ inputSchema: jsonSchema>({
+ type: 'object',
+ properties: {},
+ additionalProperties: false,
+ }),
+ async execute(): Promise {
+ const result = await apiRequest('GET', '/shells', undefined);
+ return {
+ shells: result.shells || [],
+ categories: result.categories || {},
+ count: result.count || result.shells?.length || 0,
+ };
+ },
+});
+
+// ============================================================================
+// Sessions
+// ============================================================================
+
+export type SessionStatus = 'running' | 'frozen' | 'terminated';
+
+export interface CreateSessionInput {
+ shell?: string;
+ network_mode?: NetworkMode;
+ ttl?: number;
+}
+
+export interface SessionResult {
+ session_id: string;
+ status: SessionStatus;
+ shell: string;
+ network_mode: string;
+ websocket_url?: string;
+ created_at?: string;
+ expires_at?: string;
+}
+
+/**
+ * Create an interactive shell session with WebSocket access.
+ */
+export const createSession = tool({
+ description:
+ 'Create a persistent interactive shell session with WebSocket access. Sessions persist between commands and can be frozen/unfrozen.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ shell: {
+ type: 'string',
+ description: 'Shell or REPL to start (bash, python3, node, etc.). Default: bash',
+ },
+ network_mode: {
+ type: 'string',
+ enum: ['zerotrust', 'semitrusted'],
+ description: "Network isolation mode. Default: 'zerotrust'",
+ },
+ ttl: {
+ type: 'number',
+ description: 'Time-to-live in seconds (0 = no limit). Default: 3600',
+ },
+ },
+ additionalProperties: false,
+ }),
+ async execute(input: CreateSessionInput): Promise {
+ const requestBody: Record = {};
+ if (input.shell) requestBody.shell = input.shell;
+ if (input.network_mode) requestBody.network_mode = input.network_mode;
+ if (input.ttl !== undefined) requestBody.ttl = input.ttl;
+
+ const result = await apiRequest('POST', '/sessions', requestBody);
+ return result;
+ },
+});
+
+export interface GetSessionInput {
+ session_id: string;
+}
+
+/**
+ * Get session status and details.
+ */
+export const getSession = tool({
+ description: 'Get detailed session information including status, shell, and WebSocket URL.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ session_id: {
+ type: 'string',
+ description: 'The session ID',
+ },
+ },
+ required: ['session_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: GetSessionInput): Promise {
+ return apiRequest('GET', `/sessions/${encodeURIComponent(input.session_id)}`, undefined);
+ },
+});
+
+export interface SessionSummary {
+ session_id: string;
+ status: string;
+ network_mode: string;
+ remaining_ttl?: number;
+}
+
+export interface ListSessionsResult {
+ sessions: SessionSummary[];
+}
+
+/**
+ * List all active sessions.
+ */
+export const listSessions = tool({
+ description: 'List all active interactive sessions for the authenticated API key.',
+ inputSchema: jsonSchema>({
+ type: 'object',
+ properties: {},
+ additionalProperties: false,
+ }),
+ async execute(): Promise {
+ const result = await apiRequest('GET', '/sessions', undefined);
+ return { sessions: result.sessions || [] };
+ },
+});
+
+export interface SessionCommandInput {
+ session_id: string;
+ command: string;
+}
+
+export interface SessionCommandResult {
+ stdout: string;
+ stderr: string;
+ exit_code: number;
+}
+
+/**
+ * Execute a command in a session.
+ */
+export const executeInSession = tool({
+ description: "Run a command inside a session's container and return output.",
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ session_id: {
+ type: 'string',
+ description: 'The session ID',
+ },
+ command: {
+ type: 'string',
+ description: 'The command to execute',
+ },
+ },
+ required: ['session_id', 'command'],
+ additionalProperties: false,
+ }),
+ async execute(input: SessionCommandInput): Promise {
+ return apiRequest(
+ 'POST',
+ `/sessions/${encodeURIComponent(input.session_id)}/execute`,
+ { command: input.command }
+ );
+ },
+});
+
+export interface SessionIdInput {
+ session_id: string;
+}
+
+export interface SessionStateResult {
+ session_id: string;
+ status: string;
+}
+
+/**
+ * Freeze a session to save resources.
+ */
+export const freezeSession = tool({
+ description: 'Freeze a session to save resources. Container state is preserved and can be woken later.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ session_id: { type: 'string', description: 'The session ID' },
+ },
+ required: ['session_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: SessionIdInput): Promise {
+ return apiRequest(
+ 'POST',
+ `/sessions/${encodeURIComponent(input.session_id)}/freeze`,
+ {}
+ );
+ },
+});
+
+export interface UnfreezeResult {
+ session_id: string;
+ status: string;
+ unfreeze_time_ms?: number;
+}
+
+/**
+ * Wake a frozen session.
+ */
+export const unfreezeSession = tool({
+ description: 'Wake a frozen session and restore its state.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ session_id: { type: 'string', description: 'The session ID' },
+ },
+ required: ['session_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: SessionIdInput): Promise {
+ return apiRequest(
+ 'POST',
+ `/sessions/${encodeURIComponent(input.session_id)}/unfreeze`,
+ {}
+ );
+ },
+});
+
+export interface LockResult {
+ locked: boolean;
+}
+
+/**
+ * Lock a session to prevent accidental deletion.
+ */
+export const lockSession = tool({
+ description: 'Lock a session to prevent accidental deletion.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ session_id: { type: 'string', description: 'The session ID' },
+ },
+ required: ['session_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: SessionIdInput): Promise {
+ return apiRequest(
+ 'POST',
+ `/sessions/${encodeURIComponent(input.session_id)}/lock`,
+ {}
+ );
+ },
+});
+
+/**
+ * Unlock a locked session.
+ */
+export const unlockSession = tool({
+ description: 'Unlock a locked session to allow deletion.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ session_id: { type: 'string', description: 'The session ID' },
+ },
+ required: ['session_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: SessionIdInput): Promise {
+ return apiRequest(
+ 'POST',
+ `/sessions/${encodeURIComponent(input.session_id)}/unlock`,
+ {}
+ );
+ },
+});
+
+export interface CreateSnapshotInput {
+ session_id: string;
+ name?: string;
+ hot?: boolean;
+ ttl?: number;
+}
+
+export interface SnapshotResult {
+ snapshot_id: string;
+ name?: string;
+ source_id: string;
+ source_type: string;
+ size_bytes?: number;
+ created_at?: string;
+}
+
+/**
+ * Create a snapshot of a session.
+ */
+export const createSessionSnapshot = tool({
+ description: "Create a snapshot of the session's container state.",
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ session_id: { type: 'string', description: 'The session ID' },
+ name: { type: 'string', description: 'Optional snapshot name' },
+ hot: { type: 'boolean', description: 'Create hot snapshot without stopping container. Default: false' },
+ ttl: { type: 'number', description: 'Auto-delete after N seconds (optional)' },
+ },
+ required: ['session_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: CreateSnapshotInput): Promise {
+ const body: Record = {};
+ if (input.name) body.name = input.name;
+ if (input.hot !== undefined) body.hot = input.hot;
+ if (input.ttl !== undefined) body.ttl = input.ttl;
+
+ return apiRequest(
+ 'POST',
+ `/sessions/${encodeURIComponent(input.session_id)}/snapshot`,
+ body
+ );
+ },
+});
+
+export interface RestoreSessionInput {
+ session_id: string;
+ snapshot_id: string;
+}
+
+/**
+ * Restore a session from a snapshot.
+ */
+export const restoreSession = tool({
+ description: 'Restore a session to a previous snapshot state.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ session_id: { type: 'string', description: 'The session ID' },
+ snapshot_id: { type: 'string', description: 'The snapshot ID to restore from' },
+ },
+ required: ['session_id', 'snapshot_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: RestoreSessionInput): Promise {
+ return apiRequest(
+ 'POST',
+ `/sessions/${encodeURIComponent(input.session_id)}/restore`,
+ { snapshot_id: input.snapshot_id }
+ );
+ },
+});
+
+/**
+ * Delete a session.
+ */
+export const deleteSession = tool({
+ description: 'Terminate and destroy a session permanently.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ session_id: { type: 'string', description: 'The session ID' },
+ },
+ required: ['session_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: SessionIdInput): Promise {
+ return apiRequest(
+ 'DELETE',
+ `/sessions/${encodeURIComponent(input.session_id)}`,
+ undefined
+ );
+ },
+});
+
+// ============================================================================
+// Services
+// ============================================================================
+
+export type ServiceState = 'starting' | 'running' | 'frozen' | 'redeploying' | 'failed';
+
+export interface CreateServiceInput {
+ name: string;
+ bootstrap?: string;
+ bootstrap_content?: string;
+ ports?: number[];
+ network_mode?: NetworkMode;
+ custom_domains?: string[];
+ input_files?: InputFile[];
+}
+
+export interface ServiceResult {
+ service_id: string;
+ name: string;
+ state: ServiceState;
+ url?: string;
+ ports?: number[];
+ created_at?: string;
+}
+
+/**
+ * Create a persistent service with custom subdomain.
+ */
+export const createService = tool({
+ description:
+ 'Create a long-running service with custom subdomain. Services persist until explicitly destroyed and can auto-unfreeze on HTTP requests.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ name: { type: 'string', description: 'Service name (becomes NAME.on.unsandbox.com)' },
+ bootstrap: { type: 'string', description: 'Bootstrap script content or URL' },
+ bootstrap_content: { type: 'string', description: 'Bootstrap script content (alternative to bootstrap)' },
+ ports: {
+ type: 'array',
+ items: { type: 'number' },
+ description: 'Ports to expose',
+ },
+ network_mode: {
+ type: 'string',
+ enum: ['zerotrust', 'semitrusted'],
+ description: "Network isolation mode. Default: 'semitrusted'",
+ },
+ custom_domains: {
+ type: 'array',
+ items: { type: 'string' },
+ description: 'Custom domain names',
+ },
+ input_files: {
+ type: 'array',
+ description: 'Input files to make available',
+ items: {
+ type: 'object',
+ properties: {
+ filename: { type: 'string' },
+ content: { type: 'string' },
+ },
+ required: ['filename', 'content'],
+ },
+ },
+ },
+ required: ['name'],
+ additionalProperties: false,
+ }),
+ async execute(input: CreateServiceInput): Promise {
+ return apiRequest('POST', '/services', input);
+ },
+});
+
+export interface ServiceIdInput {
+ service_id: string;
+}
+
+/**
+ * Get service status and details.
+ */
+export const getService = tool({
+ description: 'Get detailed service information including state, ports, and URL.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ service_id: { type: 'string', description: 'The service ID' },
+ },
+ required: ['service_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: ServiceIdInput): Promise {
+ return apiRequest('GET', `/services/${encodeURIComponent(input.service_id)}`, undefined);
+ },
+});
+
+export interface ServiceSummary {
+ service_id: string;
+ name: string;
+ state: string;
+ ports?: number[];
+ domains?: string[];
+}
+
+export interface ListServicesResult {
+ services: ServiceSummary[];
+}
+
+/**
+ * List all services.
+ */
+export const listServices = tool({
+ description: 'List all services for the authenticated API key.',
+ inputSchema: jsonSchema>({
+ type: 'object',
+ properties: {},
+ additionalProperties: false,
+ }),
+ async execute(): Promise {
+ const result = await apiRequest('GET', '/services', undefined);
+ return { services: result.services || [] };
+ },
+});
+
+export interface ServiceCommandInput {
+ service_id: string;
+ command: string;
+}
+
+/**
+ * Execute a command in a service.
+ */
+export const executeInService = tool({
+ description: 'Run a command inside a running service container.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ service_id: { type: 'string', description: 'The service ID' },
+ command: { type: 'string', description: 'The command to execute' },
+ },
+ required: ['service_id', 'command'],
+ additionalProperties: false,
+ }),
+ async execute(input: ServiceCommandInput): Promise {
+ return apiRequest(
+ 'POST',
+ `/services/${encodeURIComponent(input.service_id)}/execute`,
+ { command: input.command }
+ );
+ },
+});
+
+export interface ServiceStateResult {
+ service_id: string;
+ state: string;
+}
+
+/**
+ * Freeze a service to save resources.
+ */
+export const freezeService = tool({
+ description: 'Freeze a service to save resources. Auto-unfreezes on first HTTP request.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ service_id: { type: 'string', description: 'The service ID' },
+ },
+ required: ['service_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: ServiceIdInput): Promise {
+ return apiRequest(
+ 'POST',
+ `/services/${encodeURIComponent(input.service_id)}/freeze`,
+ {}
+ );
+ },
+});
+
+export interface ServiceUnfreezeResult {
+ service_id: string;
+ state: string;
+ unfreeze_time_ms?: number;
+}
+
+/**
+ * Wake a frozen service.
+ */
+export const unfreezeService = tool({
+ description: 'Manually wake a frozen service.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ service_id: { type: 'string', description: 'The service ID' },
+ },
+ required: ['service_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: ServiceIdInput): Promise {
+ return apiRequest(
+ 'POST',
+ `/services/${encodeURIComponent(input.service_id)}/unfreeze`,
+ {}
+ );
+ },
+});
+
+/**
+ * Lock a service to prevent accidental deletion.
+ */
+export const lockService = tool({
+ description: 'Lock a service to prevent accidental deletion.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ service_id: { type: 'string', description: 'The service ID' },
+ },
+ required: ['service_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: ServiceIdInput): Promise {
+ return apiRequest(
+ 'POST',
+ `/services/${encodeURIComponent(input.service_id)}/lock`,
+ {}
+ );
+ },
+});
+
+/**
+ * Unlock a locked service.
+ */
+export const unlockService = tool({
+ description: 'Unlock a locked service to allow deletion.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ service_id: { type: 'string', description: 'The service ID' },
+ },
+ required: ['service_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: ServiceIdInput): Promise {
+ return apiRequest(
+ 'POST',
+ `/services/${encodeURIComponent(input.service_id)}/unlock`,
+ {}
+ );
+ },
+});
+
+export interface RedeployServiceInput {
+ service_id: string;
+ bootstrap_content?: string;
+}
+
+/**
+ * Redeploy a service.
+ */
+export const redeployService = tool({
+ description: 'Re-run the bootstrap script. Optionally provide new bootstrap content.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ service_id: { type: 'string', description: 'The service ID' },
+ bootstrap_content: { type: 'string', description: 'New bootstrap script (optional)' },
+ },
+ required: ['service_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: RedeployServiceInput): Promise {
+ const body: Record = {};
+ if (input.bootstrap_content) body.bootstrap_content = input.bootstrap_content;
+ return apiRequest(
+ 'POST',
+ `/services/${encodeURIComponent(input.service_id)}/redeploy`,
+ body
+ );
+ },
+});
+
+export interface ServiceLogsResult {
+ log: string;
+}
+
+/**
+ * Get service logs.
+ */
+export const getServiceLogs = tool({
+ description: 'Get bootstrap and application logs for the service.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ service_id: { type: 'string', description: 'The service ID' },
+ },
+ required: ['service_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: ServiceIdInput): Promise {
+ return apiRequest(
+ 'GET',
+ `/services/${encodeURIComponent(input.service_id)}/logs`,
+ undefined
+ );
+ },
+});
+
+export interface CreateServiceSnapshotInput {
+ service_id: string;
+ name?: string;
+ hot?: boolean;
+ ttl?: number;
+}
+
+/**
+ * Create a snapshot of a service.
+ */
+export const createServiceSnapshot = tool({
+ description: "Create a snapshot of the service's container state.",
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ service_id: { type: 'string', description: 'The service ID' },
+ name: { type: 'string', description: 'Optional snapshot name' },
+ hot: { type: 'boolean', description: 'Create hot snapshot without stopping container' },
+ ttl: { type: 'number', description: 'Auto-delete after N seconds' },
+ },
+ required: ['service_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: CreateServiceSnapshotInput): Promise {
+ const body: Record = {};
+ if (input.name) body.name = input.name;
+ if (input.hot !== undefined) body.hot = input.hot;
+ if (input.ttl !== undefined) body.ttl = input.ttl;
+
+ return apiRequest(
+ 'POST',
+ `/services/${encodeURIComponent(input.service_id)}/snapshot`,
+ body
+ );
+ },
+});
+
+export interface EnvVarsResult {
+ env: Record;
+}
+
+/**
+ * Get service environment variables.
+ */
+export const getServiceEnv = tool({
+ description: 'Retrieve all environment variables set for the service.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ service_id: { type: 'string', description: 'The service ID' },
+ },
+ required: ['service_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: ServiceIdInput): Promise {
+ return apiRequest(
+ 'GET',
+ `/services/${encodeURIComponent(input.service_id)}/env`,
+ undefined
+ );
+ },
+});
+
+export interface SetServiceEnvInput {
+ service_id: string;
+ env: Record;
+}
+
+export interface SetEnvResult {
+ success: boolean;
+ env: Record;
+}
+
+/**
+ * Set service environment variables.
+ */
+export const setServiceEnv = tool({
+ description: 'Set or update environment variables for the service.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ service_id: { type: 'string', description: 'The service ID' },
+ env: {
+ type: 'object',
+ description: 'Environment variables as key-value pairs',
+ additionalProperties: { type: 'string' },
+ },
+ },
+ required: ['service_id', 'env'],
+ additionalProperties: false,
+ }),
+ async execute(input: SetServiceEnvInput): Promise {
+ return apiRequest(
+ 'PUT',
+ `/services/${encodeURIComponent(input.service_id)}/env`,
+ { env: input.env }
+ );
+ },
+});
+
+export interface DeleteServiceEnvInput {
+ service_id: string;
+ keys: string[];
+}
+
+export interface DeleteEnvResult {
+ success: boolean;
+ deleted: string[];
+}
+
+/**
+ * Delete service environment variables.
+ */
+export const deleteServiceEnv = tool({
+ description: 'Remove specific environment variables from the service.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ service_id: { type: 'string', description: 'The service ID' },
+ keys: {
+ type: 'array',
+ items: { type: 'string' },
+ description: 'Environment variable keys to delete',
+ },
+ },
+ required: ['service_id', 'keys'],
+ additionalProperties: false,
+ }),
+ async execute(input: DeleteServiceEnvInput): Promise {
+ return apiRequest(
+ 'DELETE',
+ `/services/${encodeURIComponent(input.service_id)}/env`,
+ { keys: input.keys }
+ );
+ },
+});
+
+/**
+ * Delete a service.
+ */
+export const deleteService = tool({
+ description: 'Permanently destroy a service and its container.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ service_id: { type: 'string', description: 'The service ID' },
+ },
+ required: ['service_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: ServiceIdInput): Promise {
+ return apiRequest(
+ 'DELETE',
+ `/services/${encodeURIComponent(input.service_id)}`,
+ undefined
+ );
+ },
+});
+
+// ============================================================================
+// Snapshots
+// ============================================================================
+
+export interface CreateSnapshotFromSourceInput {
+ source_type: 'session' | 'service';
+ source_id: string;
+ name?: string;
+ hot?: boolean;
+ ttl?: number;
+}
+
+/**
+ * Create a snapshot from an existing session or service.
+ */
+export const createSnapshot = tool({
+ description: 'Create a snapshot from an existing session or service.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ source_type: {
+ type: 'string',
+ enum: ['session', 'service'],
+ description: 'Type of source to snapshot',
+ },
+ source_id: { type: 'string', description: 'ID of the session or service' },
+ name: { type: 'string', description: 'Optional snapshot name' },
+ hot: { type: 'boolean', description: 'Create hot snapshot without stopping container' },
+ ttl: { type: 'number', description: 'Auto-delete after N seconds' },
+ },
+ required: ['source_type', 'source_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: CreateSnapshotFromSourceInput): Promise {
+ return apiRequest('POST', '/snapshots', input);
+ },
+});
+
+export interface SnapshotIdInput {
+ snapshot_id: string;
+}
+
+/**
+ * Get snapshot details.
+ */
+export const getSnapshot = tool({
+ description: 'Get detailed information about a snapshot.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ snapshot_id: { type: 'string', description: 'The snapshot ID' },
+ },
+ required: ['snapshot_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: SnapshotIdInput): Promise {
+ return apiRequest('GET', `/snapshots/${encodeURIComponent(input.snapshot_id)}`, undefined);
+ },
+});
+
+export interface SnapshotSummary {
+ id: string;
+ name?: string;
+ source_type: string;
+ source_id: string;
+ size_bytes?: number;
+ locked?: boolean;
+}
+
+export interface ListSnapshotsResult {
+ snapshots: SnapshotSummary[];
+}
+
+/**
+ * List all snapshots.
+ */
+export const listSnapshots = tool({
+ description: 'List all snapshots for the authenticated API key.',
+ inputSchema: jsonSchema>({
+ type: 'object',
+ properties: {},
+ additionalProperties: false,
+ }),
+ async execute(): Promise {
+ const result = await apiRequest('GET', '/snapshots', undefined);
+ return { snapshots: result.snapshots || [] };
+ },
+});
+
+/**
+ * Lock a snapshot.
+ */
+export const lockSnapshot = tool({
+ description: 'Lock a snapshot to prevent accidental deletion.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ snapshot_id: { type: 'string', description: 'The snapshot ID' },
+ },
+ required: ['snapshot_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: SnapshotIdInput): Promise {
+ return apiRequest(
+ 'POST',
+ `/snapshots/${encodeURIComponent(input.snapshot_id)}/lock`,
+ {}
+ );
+ },
+});
+
+/**
+ * Unlock a snapshot.
+ */
+export const unlockSnapshot = tool({
+ description: 'Unlock a locked snapshot to allow deletion.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ snapshot_id: { type: 'string', description: 'The snapshot ID' },
+ },
+ required: ['snapshot_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: SnapshotIdInput): Promise {
+ return apiRequest(
+ 'POST',
+ `/snapshots/${encodeURIComponent(input.snapshot_id)}/unlock`,
+ {}
+ );
+ },
+});
+
+export interface RestoreSnapshotResult {
+ snapshot_id: string;
+ status: string;
+}
+
+/**
+ * Restore a snapshot to its original source.
+ */
+export const restoreSnapshot = tool({
+ description: 'Restore a snapshot to its original source (session or service).',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ snapshot_id: { type: 'string', description: 'The snapshot ID' },
+ },
+ required: ['snapshot_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: SnapshotIdInput): Promise {
+ return apiRequest(
+ 'POST',
+ `/snapshots/${encodeURIComponent(input.snapshot_id)}/restore`,
+ {}
+ );
+ },
+});
+
+export interface CloneSnapshotInput {
+ snapshot_id: string;
+ type: 'session' | 'service';
+ name?: string;
+ shell?: string;
+}
+
+export interface CloneSnapshotResult {
+ session_id?: string;
+ service_id?: string;
+ name?: string;
+ status?: string;
+ state?: string;
+}
+
+/**
+ * Clone a snapshot to create a new session or service.
+ */
+export const cloneSnapshot = tool({
+ description: 'Create a new session or service from a snapshot.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ snapshot_id: { type: 'string', description: 'The snapshot ID' },
+ type: {
+ type: 'string',
+ enum: ['session', 'service'],
+ description: 'Type of resource to create',
+ },
+ name: { type: 'string', description: 'Service name (required for service type)' },
+ shell: { type: 'string', description: 'Shell to use (for session type)' },
+ },
+ required: ['snapshot_id', 'type'],
+ additionalProperties: false,
+ }),
+ async execute(input: CloneSnapshotInput): Promise {
+ const body: Record = { type: input.type };
+ if (input.name) body.name = input.name;
+ if (input.shell) body.shell = input.shell;
+
+ return apiRequest(
+ 'POST',
+ `/snapshots/${encodeURIComponent(input.snapshot_id)}/clone`,
+ body
+ );
+ },
+});
+
+export interface DeleteSnapshotResult {
+ snapshot_id: string;
+ status: string;
+}
+
+/**
+ * Delete a snapshot.
+ */
+export const deleteSnapshot = tool({
+ description: 'Permanently delete a snapshot.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ snapshot_id: { type: 'string', description: 'The snapshot ID' },
+ },
+ required: ['snapshot_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: SnapshotIdInput): Promise {
+ return apiRequest(
+ 'DELETE',
+ `/snapshots/${encodeURIComponent(input.snapshot_id)}`,
+ undefined
+ );
+ },
+});
+
+// ============================================================================
+// Images
+// ============================================================================
+
+export type ImageVisibility = 'private' | 'unlisted' | 'public';
+
+export interface PublishImageInput {
+ source_type: 'service' | 'snapshot';
+ source_id: string;
+ name?: string;
+ description?: string;
+}
+
+export interface ImageResult {
+ id: string;
+ name?: string;
+ description?: string;
+ fingerprint?: string;
+ source_type: string;
+ source_id: string;
+ owner_api_key?: string;
+ visibility?: ImageVisibility;
+ locked?: boolean;
+ size_bytes?: number;
+ trusted_keys?: string[];
+ node?: string;
+ created_at?: string;
+}
+
+/**
+ * Publish an image from a service or snapshot.
+ */
+export const publishImage = tool({
+ description:
+ 'Create an independent LXD image from a service or snapshot. Images survive container deletion and can be transferred between API keys.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ source_type: {
+ type: 'string',
+ enum: ['service', 'snapshot'],
+ description: 'Type of source to publish from',
+ },
+ source_id: { type: 'string', description: 'ID of the service or snapshot' },
+ name: { type: 'string', description: 'User-friendly name for the image' },
+ description: { type: 'string', description: 'Optional description' },
+ },
+ required: ['source_type', 'source_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: PublishImageInput): Promise {
+ return apiRequest('POST', '/images', input);
+ },
+});
+
+export interface ImageIdInput {
+ image_id: string;
+}
+
+/**
+ * Get image details.
+ */
+export const getImage = tool({
+ description: 'Get detailed information about an image.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ image_id: { type: 'string', description: 'The image ID' },
+ },
+ required: ['image_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: ImageIdInput): Promise {
+ return apiRequest('GET', `/images/${encodeURIComponent(input.image_id)}`, undefined);
+ },
+});
+
+export interface ImageSummary {
+ id: string;
+ name?: string;
+ fingerprint?: string;
+ visibility?: ImageVisibility;
+ locked?: boolean;
+ size_bytes?: number;
+ created_at?: string;
+}
+
+export interface ListImagesResult {
+ images: ImageSummary[];
+}
+
+/**
+ * List all images.
+ */
+export const listImages = tool({
+ description: 'List all images owned by or shared with the authenticated API key.',
+ inputSchema: jsonSchema>({
+ type: 'object',
+ properties: {},
+ additionalProperties: false,
+ }),
+ async execute(): Promise {
+ const result = await apiRequest('GET', '/images', undefined);
+ return { images: result.images || [] };
+ },
+});
+
+/**
+ * Lock an image.
+ */
+export const lockImage = tool({
+ description: 'Lock an image to prevent accidental deletion.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ image_id: { type: 'string', description: 'The image ID' },
+ },
+ required: ['image_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: ImageIdInput): Promise {
+ return apiRequest(
+ 'POST',
+ `/images/${encodeURIComponent(input.image_id)}/lock`,
+ {}
+ );
+ },
+});
+
+/**
+ * Unlock an image.
+ */
+export const unlockImage = tool({
+ description: 'Unlock a locked image to allow deletion.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ image_id: { type: 'string', description: 'The image ID' },
+ },
+ required: ['image_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: ImageIdInput): Promise {
+ return apiRequest(
+ 'POST',
+ `/images/${encodeURIComponent(input.image_id)}/unlock`,
+ {}
+ );
+ },
+});
+
+export interface GrantImageAccessInput {
+ image_id: string;
+ api_key: string;
+}
+
+/**
+ * Grant image access to another API key.
+ */
+export const grantImageAccess = tool({
+ description:
+ 'Grant access to a private/unlisted image for a specific API key. The granted user can use the image to spawn services.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ image_id: { type: 'string', description: 'The image ID' },
+ api_key: { type: 'string', description: 'API key to grant access to' },
+ },
+ required: ['image_id', 'api_key'],
+ additionalProperties: false,
+ }),
+ async execute(input: GrantImageAccessInput): Promise {
+ return apiRequest(
+ 'POST',
+ `/images/${encodeURIComponent(input.image_id)}/grant`,
+ { api_key: input.api_key }
+ );
+ },
+});
+
+/**
+ * Revoke image access from an API key.
+ */
+export const revokeImageAccess = tool({
+ description: 'Revoke previously granted access from an API key.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ image_id: { type: 'string', description: 'The image ID' },
+ api_key: { type: 'string', description: 'API key to revoke access from' },
+ },
+ required: ['image_id', 'api_key'],
+ additionalProperties: false,
+ }),
+ async execute(input: GrantImageAccessInput): Promise {
+ return apiRequest(
+ 'POST',
+ `/images/${encodeURIComponent(input.image_id)}/revoke`,
+ { api_key: input.api_key }
+ );
+ },
+});
+
+export interface TransferImageInput {
+ image_id: string;
+ to_api_key: string;
+}
+
+/**
+ * Transfer image ownership to another API key.
+ */
+export const transferImage = tool({
+ description:
+ 'Transfer full ownership of an image to another API key. The image stays on the same LXD node.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ image_id: { type: 'string', description: 'The image ID' },
+ to_api_key: { type: 'string', description: "Recipient's public API key" },
+ },
+ required: ['image_id', 'to_api_key'],
+ additionalProperties: false,
+ }),
+ async execute(input: TransferImageInput): Promise {
+ return apiRequest(
+ 'POST',
+ `/images/${encodeURIComponent(input.image_id)}/transfer`,
+ { to_api_key: input.to_api_key }
+ );
+ },
+});
+
+export interface SetImageVisibilityInput {
+ image_id: string;
+ visibility: ImageVisibility;
+}
+
+/**
+ * Set image visibility.
+ */
+export const setImageVisibility = tool({
+ description:
+ 'Control who can see and use this image. Options: private (only owner), unlisted (shareable via trust), public (visible to all).',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ image_id: { type: 'string', description: 'The image ID' },
+ visibility: {
+ type: 'string',
+ enum: ['private', 'unlisted', 'public'],
+ description: 'Visibility setting',
+ },
+ },
+ required: ['image_id', 'visibility'],
+ additionalProperties: false,
+ }),
+ async execute(input: SetImageVisibilityInput): Promise {
+ return apiRequest(
+ 'POST',
+ `/images/${encodeURIComponent(input.image_id)}/visibility`,
+ { visibility: input.visibility }
+ );
+ },
+});
+
+export interface SpawnFromImageInput {
+ image_id: string;
+ name?: string;
+ network_mode?: NetworkMode;
+ ports?: number[];
+ bootstrap?: string;
+}
+
+export interface SpawnFromImageResult {
+ service_id: string;
+ name?: string;
+ source_image: string;
+ state: string;
+}
+
+/**
+ * Spawn a service from an image.
+ */
+export const spawnFromImage = tool({
+ description: 'Create a new service using this image as the base.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ image_id: { type: 'string', description: 'The image ID' },
+ name: { type: 'string', description: 'Service name' },
+ network_mode: {
+ type: 'string',
+ enum: ['zerotrust', 'semitrusted'],
+ description: "Network isolation mode. Default: 'zerotrust'",
+ },
+ ports: {
+ type: 'array',
+ items: { type: 'number' },
+ description: 'Ports to expose',
+ },
+ bootstrap: { type: 'string', description: 'Optional bootstrap script' },
+ },
+ required: ['image_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: SpawnFromImageInput): Promise {
+ const body: Record = {};
+ if (input.name) body.name = input.name;
+ if (input.network_mode) body.network_mode = input.network_mode;
+ if (input.ports) body.ports = input.ports;
+ if (input.bootstrap) body.bootstrap = input.bootstrap;
+
+ return apiRequest(
+ 'POST',
+ `/images/${encodeURIComponent(input.image_id)}/spawn`,
+ body
+ );
+ },
+});
+
+export interface TrustedKeysResult {
+ trusted_keys: string[];
+}
+
+/**
+ * List trusted API keys for an image.
+ */
+export const getImageTrustedKeys = tool({
+ description: 'List all API keys that have been granted access to this image.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ image_id: { type: 'string', description: 'The image ID' },
+ },
+ required: ['image_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: ImageIdInput): Promise {
+ return apiRequest(
+ 'GET',
+ `/images/${encodeURIComponent(input.image_id)}/trusted`,
+ undefined
+ );
+ },
+});
+
+export interface DeleteImageResult {
+ success: boolean;
+ message: string;
+}
+
+/**
+ * Delete an image.
+ */
+export const deleteImage = tool({
+ description: 'Permanently delete an image from LXD and the database. Cannot delete locked images.',
+ inputSchema: jsonSchema({
+ type: 'object',
+ properties: {
+ image_id: { type: 'string', description: 'The image ID' },
+ },
+ required: ['image_id'],
+ additionalProperties: false,
+ }),
+ async execute(input: ImageIdInput): Promise {
+ return apiRequest(
+ 'DELETE',
+ `/images/${encodeURIComponent(input.image_id)}`,
+ undefined
+ );
+ },
+});
+
+// ============================================================================
+// System
+// ============================================================================
+
+export interface HealthResult {
+ status: string;
+}
+
+/**
+ * Health check.
+ */
+export const healthCheck = tool({
+ description: 'Simple health check endpoint to verify the Unsandbox API is operational.',
+ inputSchema: jsonSchema>({
+ type: 'object',
+ properties: {},
+ additionalProperties: false,
+ }),
+ async execute(): Promise {
+ return apiRequest('GET', '/health', undefined);
+ },
+});
+
+export interface ClusterStatusResult {
+ mode: string;
+ network_mode: string;
+ pool_size: number;
+ available: number;
+ allocated: number;
+ spawning: number;
+ total_containers: number;
+ network_breakdown?: {
+ zerotrust?: { total: number; available: number; allocated: number };
+ semitrusted?: { total: number; available: number; allocated: number; services?: number };
+ };
+}
+
+/**
+ * Get cluster status.
+ */
+export const getClusterStatus = tool({
+ description: 'Get information about the container pool and system status.',
+ inputSchema: jsonSchema>({
+ type: 'object',
+ properties: {},
+ additionalProperties: false,
+ }),
+ async execute(): Promise {
+ return apiRequest('GET', '/cluster', undefined);
+ },
+});
+
+export interface SystemStatsResult {
+ containers: number;
+ load_1min: string;
+ load_5min: string;
+ load_15min: string;
+}
+
+/**
+ * Get system statistics.
+ */
+export const getSystemStats = tool({
+ description: 'Get detailed system statistics including load and container metrics.',
+ inputSchema: jsonSchema>({
+ type: 'object',
+ properties: {},
+ additionalProperties: false,
+ }),
+ async execute(): Promise {
+ return apiRequest('GET', '/stats', undefined);
+ },
+});
+
+export interface PoolInfo {
+ id: string;
+ total: number;
+ available: number;
+ allocated: number;
+}
+
+export interface ListPoolsResult {
+ pools: PoolInfo[];
+ pool_count: number;
+ total_capacity: number;
+ total_available: number;
+ total_allocated: number;
+}
+
+/**
+ * List all pools.
+ */
+export const listPools = tool({
+ description: 'Get status of all registered container pools (horizontal scaling).',
+ inputSchema: jsonSchema>({
+ type: 'object',
+ properties: {},
+ additionalProperties: false,
+ }),
+ async execute(): Promise {
+ return apiRequest('GET', '/pools', undefined);
+ },
+});
+
+// ============================================================================
+// Default Export
+// ============================================================================
+
+// Default export with all tools for convenience
export default {
+ // Execution
executeCodeAsync,
getJob,
execute,
@@ -692,4 +2484,62 @@ export default {
runAsync,
listJobs,
deleteJob,
+ // Languages
+ getLanguages,
+ getShells,
+ // Sessions
+ createSession,
+ getSession,
+ listSessions,
+ executeInSession,
+ freezeSession,
+ unfreezeSession,
+ lockSession,
+ unlockSession,
+ createSessionSnapshot,
+ restoreSession,
+ deleteSession,
+ // Services
+ createService,
+ getService,
+ listServices,
+ executeInService,
+ freezeService,
+ unfreezeService,
+ lockService,
+ unlockService,
+ redeployService,
+ getServiceLogs,
+ createServiceSnapshot,
+ getServiceEnv,
+ setServiceEnv,
+ deleteServiceEnv,
+ deleteService,
+ // Snapshots
+ createSnapshot,
+ getSnapshot,
+ listSnapshots,
+ lockSnapshot,
+ unlockSnapshot,
+ restoreSnapshot,
+ cloneSnapshot,
+ deleteSnapshot,
+ // Images
+ publishImage,
+ getImage,
+ listImages,
+ lockImage,
+ unlockImage,
+ grantImageAccess,
+ revokeImageAccess,
+ transferImage,
+ setImageVisibility,
+ spawnFromImage,
+ getImageTrustedKeys,
+ deleteImage,
+ // System
+ healthCheck,
+ getClusterStatus,
+ getSystemStats,
+ listPools,
};