From ad469432708e308b7197e5bc305e545d1de847bb Mon Sep 17 00:00:00 2001 From: Ajax Davis Date: Thu, 15 Jan 2026 06:43:40 +1000 Subject: [PATCH] feat(unsandbox): add 3 more tools - runAsync, listJobs, deleteJob Complete unsandbox API coverage with 7 total tools: - executeCodeAsync: async code execution - execute: sync code execution - run: sync with shebang auto-detect - runAsync: async with shebang auto-detect - getJob: get job status/results - listJobs: list all active jobs - deleteJob: cancel a job --- packages/tools/official/blocks.yml | 69 +++++++ packages/tools/official/unsandbox/block.ts | 13 +- .../tools/official/unsandbox/src/index.ts | 192 ++++++++++++++++++ 3 files changed, 273 insertions(+), 1 deletion(-) diff --git a/packages/tools/official/blocks.yml b/packages/tools/official/blocks.yml index 160383d..b45584f 100644 --- a/packages/tools/official/blocks.yml +++ b/packages/tools/official/blocks.yml @@ -5166,6 +5166,75 @@ blocks: description: "Execution duration in milliseconds" measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance, readme_documentation] + unsandbox.runAsync: + type: utility + description: "Execute code asynchronously with automatic language detection from shebang. Returns a job_id immediately. Use getJob to check status and retrieve results." + path: "unsandbox" + domain_rules: + - id: api_integration + description: "Must call Unsandbox API POST /run/async 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: job_id + type: string + description: "Job ID to use with getJob to retrieve results" + - name: status + type: string + description: "Initial job status" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance, readme_documentation] + + unsandbox.listJobs: + type: utility + description: "List all active (pending or running) code execution jobs. Returns an array of job objects with their current status." + path: "unsandbox" + domain_rules: + - id: api_integration + description: "Must call Unsandbox API GET /jobs endpoint" + inputs: [] + outputs: + - name: jobs + type: Job[] + description: "Array of active jobs with id, status, language, and createdAt" + - name: count + type: number + description: "Number of active jobs" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance, readme_documentation] + + unsandbox.deleteJob: + type: utility + description: "Cancel a pending or running code execution job. The job will be terminated and resources freed." + path: "unsandbox" + domain_rules: + - id: api_integration + description: "Must call Unsandbox API DELETE /jobs/{id} endpoint" + - id: status_validation + description: "Should handle cases where job is already completed or not found" + inputs: + - name: job_id + type: string + description: "The job ID to cancel" + outputs: + - name: deleted + type: boolean + description: "Whether the job was successfully cancelled" + - name: job_id + type: string + description: "The cancelled job ID" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance, readme_documentation] + # ============================================================================= # VALIDATORS - Which validators to run against each block # ============================================================================= diff --git a/packages/tools/official/unsandbox/block.ts b/packages/tools/official/unsandbox/block.ts index 1589101..83fa8c3 100644 --- a/packages/tools/official/unsandbox/block.ts +++ b/packages/tools/official/unsandbox/block.ts @@ -2,7 +2,15 @@ * Block metadata for unsandbox tools * This file provides metadata for the blocks validator */ -import { execute, executeCodeAsync, getJob, run } from './src/index.js'; +import { + deleteJob, + execute, + executeCodeAsync, + getJob, + listJobs, + run, + runAsync, +} from './src/index.js'; export const block = { name: 'unsandbox', @@ -12,6 +20,9 @@ export const block = { getJob, execute, run, + runAsync, + listJobs, + deleteJob, }, }; diff --git a/packages/tools/official/unsandbox/src/index.ts b/packages/tools/official/unsandbox/src/index.ts index 57d8bde..ebedc22 100644 --- a/packages/tools/official/unsandbox/src/index.ts +++ b/packages/tools/official/unsandbox/src/index.ts @@ -494,10 +494,202 @@ export const run = tool({ }, }); +/** + * Additional interfaces for job management + */ +export interface RunAsyncInput { + code: string; + network_mode?: NetworkMode; + ttl?: number; +} + +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; +} + +/** + * Execute code asynchronously with automatic language detection from shebang. + * 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.', + inputSchema: jsonSchema({ + 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: RunAsyncInput): Promise { + 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'); + } + + const url = new URL(`${UNSANDBOX_API_BASE}/run/async`); + 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 { job_id: string; status?: string }; + + return { + job_id: result.job_id, + status: result.status || 'queued', + }; + }, +}); + +/** + * List all active (pending or running) code execution jobs. + */ +export const listJobs = tool({ + description: + 'List all active (pending or running) code execution jobs. Returns an array of job objects with their current status.', + inputSchema: jsonSchema>({ + type: 'object', + properties: {}, + additionalProperties: false, + }), + async execute(): Promise { + const apiKey = getApiKey(); + + 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, + })); + + return { + jobs, + count: jobs.length, + }; + }, +}); + +/** + * Cancel a pending or running code execution job. + */ +export const deleteJob = tool({ + description: + 'Cancel a pending or running code execution job. The job will be terminated and resources freed.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + job_id: { + type: 'string', + description: 'The job ID to cancel', + }, + }, + required: ['job_id'], + 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}`); + } + + return { + deleted: true, + job_id: input.job_id, + }; + }, +}); + // Default export for convenience export default { executeCodeAsync, getJob, execute, run, + runAsync, + listJobs, + deleteJob, };