feat(unsandbox): add code execution tools for unsandbox API

- Add 4 new tools: executeCodeAsync, getJob, execute, run
- Support 42+ programming languages
- Features: network isolation modes, input files, compiled artifacts, WASM
- Add entity definitions for code execution results
- All tools pass schema and shape validation
This commit is contained in:
Ajax Davis 2026-01-15 06:38:45 +10:00
parent 54d39c38fa
commit 4461bf70c8
8 changed files with 889 additions and 0 deletions

View file

@ -367,6 +367,33 @@ domain:
fields: [mode, allowedDomains, rules]
description: "DNS-based network filtering configuration"
# -------------------------------------------------------------------------
# Code execution entities (unsandbox)
# -------------------------------------------------------------------------
code_execution_result:
fields: [stdout, stderr, exitCode, duration, language]
description: "Result of executing code in a sandbox"
code_execution_job:
fields: [jobId, status, createdAt, language]
description: "Async code execution job reference"
code_job_result:
fields: [jobId, status, stdout, stderr, exitCode, duration, error]
description: "Completed job result with output"
input_file:
fields: [filename, content]
description: "File to make available in sandbox /tmp/input/"
compiled_artifact:
fields: [binary, format, size]
description: "Compiled binary artifact from code execution"
wasm_artifact:
fields: [wasm, format, size]
description: "WebAssembly compiled artifact"
# -------------------------------------------------------------------------
# Agent & workflow entities
# -------------------------------------------------------------------------
@ -4951,6 +4978,194 @@ blocks:
description: "Whether the policy was successfully applied"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance, readme_documentation]
# ---------------------------------------------------------------------------
# M) Code Execution - Unsandbox (4 tools)
# ---------------------------------------------------------------------------
unsandbox.executeCodeAsync:
type: utility
description: "Execute code asynchronously in a secure sandbox. Returns a job_id immediately. Use getJob to check status and retrieve results. Supports 42+ languages including Python, JavaScript, TypeScript, Go, Rust, C, C++, Java, Ruby, and more."
path: "unsandbox"
domain_rules:
- id: api_integration
description: "Must call Unsandbox API POST /execute/async endpoint"
- id: language_support
description: |
Must support these languages: python, javascript, typescript, ruby, perl, php, lua, bash, r,
elixir, erlang, tcl, scheme, powershell, clojure, commonlisp, crystal, groovy, deno, awk, raku,
c, cpp, go, rust, java, kotlin, cobol, fortran, d, zig, nim, v, objc, dart, julia, haskell,
ocaml, fsharp, csharp, prolog, forth
- id: network_modes
description: "Must support 'zerotrust' (default, blocks all network) and 'semitrusted' (allows outbound)"
- id: input_files
description: "Must support optional input_files array with filename and base64 content"
inputs:
- name: language
type: string
description: "Programming language to execute (e.g., 'python', 'javascript', 'go', 'rust')"
- name: code
type: string
description: "The source code to execute"
- name: input_files
type: InputFile[]
optional: true
description: "Optional array of input files to make available in /tmp/input/"
- name: network_mode
type: "'zerotrust' | 'semitrusted'"
optional: true
description: "Network isolation mode. 'zerotrust' (default) blocks all network. 'semitrusted' allows outbound."
- name: ttl
type: number
optional: true
description: "Execution timeout in seconds (1-900). Default: 60."
- name: return_artifact
type: boolean
optional: true
description: "For compiled languages, return the compiled binary"
- name: return_wasm_artifact
type: boolean
optional: true
description: "Compile to WebAssembly. Supported for C, C++, Rust, Zig, Go."
outputs:
- name: job_id
type: string
description: "Job ID to use with getJob to retrieve results"
- name: status
type: string
description: "Initial job status (typically 'queued' or 'running')"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance, readme_documentation]
unsandbox.getJob:
type: utility
description: "Get the status and results of an async code execution job. Poll this endpoint until status is 'completed' or 'failed'."
path: "unsandbox"
domain_rules:
- id: api_integration
description: "Must call Unsandbox API GET /jobs/{job_id} endpoint"
- id: status_handling
description: "Must handle all status values: queued, running, completed, failed"
inputs:
- name: job_id
type: string
description: "The job ID returned from executeCodeAsync"
outputs:
- name: job_id
type: string
description: "The job ID"
- name: status
type: string
description: "Job status: 'queued', 'running', 'completed', or 'failed'"
- name: stdout
type: string
optional: true
description: "Standard output from execution (when completed)"
- name: stderr
type: string
optional: true
description: "Standard error from execution (when completed)"
- name: exit_code
type: number
optional: true
description: "Exit code from execution (when completed)"
- name: duration_ms
type: number
optional: true
description: "Execution duration in milliseconds (when completed)"
- name: error
type: string
optional: true
description: "Error message (when failed)"
- name: artifact
type: string
optional: true
description: "Base64-encoded compiled binary (if return_artifact was true)"
- name: wasm_artifact
type: string
optional: true
description: "Base64-encoded WebAssembly binary (if return_wasm_artifact was true)"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance, readme_documentation]
unsandbox.execute:
type: utility
description: "Execute code synchronously in a secure sandbox. Blocks until execution completes and returns results directly. Best for quick scripts under 60 seconds."
path: "unsandbox"
domain_rules:
- id: api_integration
description: "Must call Unsandbox API POST /execute endpoint (sync)"
- id: timeout_handling
description: "Must handle execution timeout gracefully"
inputs:
- name: language
type: string
description: "Programming language to execute"
- name: code
type: string
description: "The source code to execute"
- name: input_files
type: InputFile[]
optional: true
description: "Optional array of input files to make available in /tmp/input/"
- name: network_mode
type: "'zerotrust' | 'semitrusted'"
optional: true
description: "Network isolation mode. Default: 'zerotrust'"
- name: ttl
type: number
optional: true
description: "Execution timeout in seconds (1-900). Default: 60."
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: "Exit code from execution"
- name: duration_ms
type: number
description: "Execution duration in milliseconds"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance, readme_documentation]
unsandbox.run:
type: utility
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."
path: "unsandbox"
domain_rules:
- id: api_integration
description: "Must call Unsandbox API POST /run endpoint with text/plain content type"
- id: shebang_detection
description: "Language is auto-detected from shebang line in code"
inputs:
- name: code
type: string
description: "The source code with shebang line (e.g., #!/usr/bin/env python)"
- name: network_mode
type: "'zerotrust' | 'semitrusted'"
optional: true
description: "Network isolation mode. Default: 'zerotrust'"
- name: ttl
type: number
optional: true
description: "Execution timeout in seconds (1-900). Default: 60."
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: "Exit code from execution"
- name: detected_language
type: string
description: "Language detected from shebang"
- name: duration_ms
type: number
description: "Execution duration in milliseconds"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance, readme_documentation]
# =============================================================================
# VALIDATORS - Which validators to run against each block
# =============================================================================

View file

@ -0,0 +1,18 @@
/**
* Block metadata for unsandbox tools
* This file provides metadata for the blocks validator
*/
import { execute, executeCodeAsync, getJob, run } from './src/index.js';
export const block = {
name: 'unsandbox',
description: 'Execute code in a secure sandbox environment supporting 42+ languages',
tools: {
executeCodeAsync,
getJob,
execute,
run,
},
};
export default block;

View file

@ -0,0 +1,6 @@
/**
* Unsandbox Code Execution Tools
* Re-exports from src/index.ts for blocks validator compatibility
*/
export * from './src/index.js';
export { default } from './src/index.js';

View file

@ -0,0 +1,110 @@
{
"name": "@tpmjs/tools-unsandbox",
"version": "0.1.0",
"description": "Execute code in a secure sandbox environment. Supports 42+ programming languages with async execution, input files, and compiled artifacts.",
"type": "module",
"keywords": [
"tpmjs",
"code-execution",
"sandbox",
"unsandbox",
"ai"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"type-check": "tsc --noEmit",
"clean": "rm -rf dist .turbo"
},
"devDependencies": {
"@tpmjs/tsconfig": "workspace:*",
"tsup": "^8.5.1",
"typescript": "^5.9.3"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/tpmjs/tpmjs.git",
"directory": "packages/tools/official/unsandbox"
},
"homepage": "https://tpmjs.com",
"license": "MIT",
"tpmjs": {
"category": "code-execution",
"frameworks": [
"vercel-ai"
],
"tools": [
{
"name": "executeCodeAsync",
"description": "Execute code asynchronously in a secure sandbox. Returns a job_id immediately. Use getJob to check status and retrieve results.",
"parameters": [
{
"name": "language",
"type": "string",
"description": "Programming language to execute",
"required": true
},
{
"name": "code",
"type": "string",
"description": "The source code to execute",
"required": true
},
{
"name": "input_files",
"type": "array",
"description": "Optional array of input files",
"required": false
},
{
"name": "network_mode",
"type": "string",
"description": "Network isolation mode: zerotrust or semitrusted",
"required": false
},
{
"name": "ttl",
"type": "number",
"description": "Execution timeout in seconds (1-900)",
"required": false
}
],
"returns": {
"type": "object",
"description": "Job ID and initial status"
}
},
{
"name": "getJob",
"description": "Get the status and results of an async code execution job",
"parameters": [
{
"name": "job_id",
"type": "string",
"description": "The job ID returned from executeCodeAsync",
"required": true
}
],
"returns": {
"type": "object",
"description": "Job status and results"
}
}
]
},
"dependencies": {
"ai": "6.0.23"
}
}

View file

@ -0,0 +1,503 @@
/**
* Unsandbox Code Execution Tools for TPMJS
* Execute code in a secure sandbox environment supporting 42+ languages.
*
* @requires UNSANDBOX_API_KEY environment variable
*/
import { jsonSchema, tool } from 'ai';
const UNSANDBOX_API_BASE = 'https://api.unsandbox.com';
/**
* Supported programming languages
*/
export const SUPPORTED_LANGUAGES = [
'python',
'javascript',
'typescript',
'ruby',
'perl',
'php',
'lua',
'bash',
'r',
'elixir',
'erlang',
'tcl',
'scheme',
'powershell',
'clojure',
'commonlisp',
'crystal',
'groovy',
'deno',
'awk',
'raku',
'c',
'cpp',
'go',
'rust',
'java',
'kotlin',
'cobol',
'fortran',
'd',
'zig',
'nim',
'v',
'objc',
'dart',
'julia',
'haskell',
'ocaml',
'fsharp',
'csharp',
'prolog',
'forth',
] as const;
export type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number];
export type NetworkMode = 'zerotrust' | 'semitrusted';
export interface InputFile {
filename: string;
content: string; // Base64 encoded
}
export interface ExecuteAsyncInput {
language: string;
code: string;
input_files?: InputFile[];
network_mode?: NetworkMode;
ttl?: 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;
}
/**
* Execute code asynchronously in a secure sandbox.
* Returns a job_id immediately. Use getJob to check status and retrieve results.
* Supports 42+ languages including Python, JavaScript, TypeScript, Go, Rust, C, C++, Java, Ruby, and more.
*/
export const executeCodeAsync = tool({
description:
'Execute code asynchronously in a secure sandbox. Returns a job_id immediately. Use getJob to check status and retrieve results. Supports 42+ languages including Python, JavaScript, TypeScript, Go, Rust, C, C++, Java, Ruby, and more.',
inputSchema: jsonSchema<ExecuteAsyncInput>({
type: 'object',
properties: {
language: {
type: 'string',
description:
'Programming language to execute. Supported: python, javascript, typescript, ruby, perl, php, lua, bash, r, elixir, erlang, tcl, scheme, powershell, clojure, commonlisp, crystal, groovy, deno, awk, raku, c, cpp, go, rust, java, kotlin, cobol, fortran, d, zig, nim, v, objc, dart, julia, haskell, ocaml, fsharp, csharp, prolog, forth.',
},
code: {
type: 'string',
description: 'The source code to execute.',
},
input_files: {
type: 'array',
description: 'Optional array of input files to make available in /tmp/input/.',
items: {
type: 'object',
properties: {
filename: {
type: 'string',
description: 'Name of the file.',
},
content: {
type: 'string',
description: 'Base64-encoded file content.',
},
},
required: ['filename', 'content'],
},
},
network_mode: {
type: 'string',
enum: ['zerotrust', 'semitrusted'],
description:
'Network isolation mode. "zerotrust" (default) blocks all network. "semitrusted" allows outbound.',
},
ttl: {
type: 'number',
description: 'Execution timeout in seconds (1-900). Default: 60.',
},
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: ExecuteAsyncInput): Promise<ExecuteAsyncResult> {
const apiKey = getApiKey();
// Validate language
if (!input.language || typeof input.language !== 'string') {
throw new Error('Language is required and must be a string');
}
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)) {
throw new Error('TTL must be between 1 and 900 seconds');
}
const requestBody: Record<string, unknown> = {
language: input.language.toLowerCase(),
code: input.code,
};
if (input.input_files) {
requestBody.input_files = input.input_files;
}
if (input.network_mode) {
requestBody.network_mode = input.network_mode;
}
if (input.ttl) {
requestBody.ttl = input.ttl;
}
if (input.return_artifact) {
requestBody.return_artifact = input.return_artifact;
}
if (input.return_wasm_artifact) {
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 };
return {
job_id: result.job_id,
status: result.status || 'queued',
};
},
});
/**
* 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'.",
inputSchema: jsonSchema<GetJobInput>({
type: 'object',
properties: {
job_id: {
type: 'string',
description: 'The job ID returned from executeCodeAsync',
},
},
required: ['job_id'],
additionalProperties: false,
}),
async execute(input: GetJobInput): Promise<GetJobResult> {
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;
return {
job_id: result.job_id || input.job_id,
status: result.status,
stdout: result.stdout,
stderr: result.stderr,
exit_code: result.exit_code,
duration_ms: result.duration_ms,
error: result.error,
artifact: result.artifact,
wasm_artifact: result.wasm_artifact,
};
},
});
/**
* Sync execution interfaces
*/
export interface ExecuteSyncInput {
language: string;
code: string;
input_files?: InputFile[];
network_mode?: NetworkMode;
ttl?: number;
}
export interface ExecuteSyncResult {
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;
}
/**
* Execute code synchronously in a secure sandbox.
* Blocks until execution completes and returns results directly.
* Best for quick scripts under 60 seconds.
*/
export const execute = tool({
description:
'Execute code synchronously in a secure sandbox. Blocks until execution completes and returns results directly. Best for quick scripts under 60 seconds.',
inputSchema: jsonSchema<ExecuteSyncInput>({
type: 'object',
properties: {
language: {
type: 'string',
description: 'Programming language to execute',
},
code: {
type: 'string',
description: 'The source code to execute',
},
input_files: {
type: 'array',
description: 'Optional array of input files to make available in /tmp/input/',
items: {
type: 'object',
properties: {
filename: { type: 'string' },
content: { type: 'string' },
},
required: ['filename', 'content'],
},
},
network_mode: {
type: 'string',
enum: ['zerotrust', 'semitrusted'],
description: "Network isolation mode. Default: 'zerotrust'",
},
ttl: {
type: 'number',
description: 'Execution timeout in seconds (1-900). Default: 60.',
},
},
required: ['language', 'code'],
additionalProperties: false,
}),
async execute(input: ExecuteSyncInput): Promise<ExecuteSyncResult> {
const apiKey = getApiKey();
if (!input.language || typeof input.language !== 'string') {
throw new Error('Language is required and must be a string');
}
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)) {
throw new Error('TTL must be between 1 and 900 seconds');
}
const requestBody: Record<string, unknown> = {
language: input.language.toLowerCase(),
code: input.code,
};
if (input.input_files) {
requestBody.input_files = input.input_files;
}
if (input.network_mode) {
requestBody.network_mode = input.network_mode;
}
if (input.ttl) {
requestBody.ttl = input.ttl;
}
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}`);
}
const result = (await response.json()) as ExecuteSyncResult;
return {
stdout: result.stdout || '',
stderr: result.stderr || '',
exit_code: result.exit_code ?? 0,
duration_ms: result.duration_ms || 0,
};
},
});
/**
* 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.
*/
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.',
inputSchema: jsonSchema<RunInput>({
type: 'object',
properties: {
code: {
type: 'string',
description: 'The source code with shebang line (e.g., #!/usr/bin/env python)',
},
network_mode: {
type: 'string',
enum: ['zerotrust', 'semitrusted'],
description: "Network isolation mode. Default: 'zerotrust'",
},
ttl: {
type: 'number',
description: 'Execution timeout in seconds (1-900). Default: 60.',
},
},
required: ['code'],
additionalProperties: false,
}),
async execute(input: RunInput): Promise<RunResult> {
const apiKey = getApiKey();
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)) {
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`);
if (input.network_mode) {
url.searchParams.set('network_mode', input.network_mode);
}
if (input.ttl) {
url.searchParams.set('ttl', input.ttl.toString());
}
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;
return {
stdout: result.stdout || '',
stderr: result.stderr || '',
exit_code: result.exit_code ?? 0,
detected_language: result.detected_language || 'unknown',
duration_ms: result.duration_ms || 0,
};
},
});
// Default export for convenience
export default {
executeCodeAsync,
getJob,
execute,
run,
};

View file

@ -0,0 +1,11 @@
{
"extends": "@tpmjs/tsconfig/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"incremental": false,
"composite": false
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

View file

@ -0,0 +1,10 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
clean: true,
treeshake: true,
splitting: false,
});

16
pnpm-lock.yaml generated
View file

@ -3540,6 +3540,22 @@ importers:
specifier: ^5.9.3
version: 5.9.3
packages/tools/official/unsandbox:
dependencies:
ai:
specifier: 6.0.23
version: 6.0.23(zod@4.3.5)
devDependencies:
'@tpmjs/tsconfig':
specifier: workspace:*
version: link:../../../config/tsconfig
tsup:
specifier: ^8.5.1
version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
typescript:
specifier: ^5.9.3
version: 5.9.3
packages/tools/official/url-normalize:
dependencies:
ai: