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:
parent
54d39c38fa
commit
4461bf70c8
8 changed files with 889 additions and 0 deletions
|
|
@ -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
|
||||
# =============================================================================
|
||||
|
|
|
|||
18
packages/tools/official/unsandbox/block.ts
Normal file
18
packages/tools/official/unsandbox/block.ts
Normal 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;
|
||||
6
packages/tools/official/unsandbox/index.ts
Normal file
6
packages/tools/official/unsandbox/index.ts
Normal 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';
|
||||
110
packages/tools/official/unsandbox/package.json
Normal file
110
packages/tools/official/unsandbox/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
503
packages/tools/official/unsandbox/src/index.ts
Normal file
503
packages/tools/official/unsandbox/src/index.ts
Normal 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,
|
||||
};
|
||||
11
packages/tools/official/unsandbox/tsconfig.json
Normal file
11
packages/tools/official/unsandbox/tsconfig.json
Normal 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"]
|
||||
}
|
||||
10
packages/tools/official/unsandbox/tsup.config.ts
Normal file
10
packages/tools/official/unsandbox/tsup.config.ts
Normal 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
16
pnpm-lock.yaml
generated
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue