tpmjs/templates/unsandbox-executor/executor.js
Ajax Davis ee066a20ca Add Unsandbox executor template
Adds a new executor template for deploying TPMJS tools on Unsandbox,
providing an alternative to the Vercel executor.

Features:
- One-command deploy via `un` CLI
- API-compatible with Vercel executor
- Standalone bootstrap script (no network required during bootstrap)
- Full documentation with examples
2026-02-03 21:29:22 +10:00

351 lines
9.6 KiB
JavaScript

#!/usr/bin/env node
/**
* TPMJS Executor for Unsandbox
*
* A lightweight HTTP server that executes TPMJS tools in isolated Unsandbox containers.
* Since Unsandbox IS the sandbox, we don't need an additional isolation layer.
*
* API-compatible with the Vercel executor.
*/
const http = require('http');
const { execSync, spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
const PORT = process.env.PORT || 80;
const API_KEY = process.env.EXECUTOR_API_KEY || null;
// CORS headers for cross-origin requests
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
};
/**
* Send JSON response with proper headers
*/
function jsonResponse(res, statusCode, data) {
res.writeHead(statusCode, {
'Content-Type': 'application/json',
...corsHeaders,
});
res.end(JSON.stringify(data));
}
/**
* Validate API key if configured
*/
function checkAuth(req) {
if (!API_KEY) return true;
const authHeader = req.headers.authorization;
return authHeader === `Bearer ${API_KEY}`;
}
/**
* Parse JSON request body
*/
function parseBody(req) {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', (chunk) => (body += chunk));
req.on('end', () => {
try {
resolve(body ? JSON.parse(body) : {});
} catch (e) {
reject(new Error('Invalid JSON'));
}
});
req.on('error', reject);
});
}
/**
* GET /api/health - Health check endpoint
*/
function handleHealth(req, res) {
jsonResponse(res, 200, {
status: 'ok',
version: '1.0.0',
info: {
runtime: 'unsandbox',
timestamp: new Date().toISOString(),
},
});
}
/**
* POST /api/execute-tool - Execute a TPMJS tool
*
* Request body:
* {
* packageName: string, // npm package name (e.g., "@tpmjs/hello")
* name: string, // tool export name (e.g., "helloWorldTool")
* version?: string, // package version (default: "latest")
* params: object, // parameters to pass to tool.execute()
* env?: object // environment variables for the tool
* }
*/
async function handleExecuteTool(req, res) {
const startTime = Date.now();
// Check authorization
if (!checkAuth(req)) {
return jsonResponse(res, 401, {
success: false,
error: 'Unauthorized',
executionTimeMs: Date.now() - startTime,
});
}
// Parse request body
let body;
try {
body = await parseBody(req);
} catch (e) {
return jsonResponse(res, 400, {
success: false,
error: 'Invalid JSON body',
executionTimeMs: Date.now() - startTime,
});
}
const { packageName, name, version = 'latest', params = {}, env } = body;
// Validate required fields
if (!packageName || !name) {
return jsonResponse(res, 400, {
success: false,
error: 'Missing required fields: packageName, name',
executionTimeMs: Date.now() - startTime,
});
}
const packageSpec = `${packageName}@${version}`;
const workDir = `/tmp/tpmjs-exec-${Date.now()}-${Math.random().toString(36).slice(2)}`;
try {
// Create isolated work directory
fs.mkdirSync(workDir, { recursive: true });
// Initialize package.json
fs.writeFileSync(
path.join(workDir, 'package.json'),
JSON.stringify({
name: 'tpmjs-execution',
private: true,
type: 'commonjs',
})
);
// Install the npm package
console.log(`[executor] Installing ${packageSpec}...`);
const installStart = Date.now();
try {
execSync(`npm install --no-save --omit=dev --no-audit --no-fund ${packageSpec}`, {
cwd: workDir,
stdio: ['pipe', 'pipe', 'pipe'],
timeout: 60000, // 60s timeout for install
});
} catch (installError) {
console.error(`[executor] npm install failed:`, installError.message);
return jsonResponse(res, 500, {
success: false,
error: `npm install failed: ${installError.message}`,
stderr: installError.stderr?.toString(),
executionTimeMs: Date.now() - startTime,
});
}
console.log(`[executor] npm install completed in ${Date.now() - installStart}ms`);
// Build environment variable setup code
const envSetup = env
? Object.entries(env)
.map(([key, value]) => `process.env[${JSON.stringify(key)}] = ${JSON.stringify(value)};`)
.join('\n')
: '';
// Generate the execution script
// This script loads the tool and calls tool.execute(params)
const script = `
${envSetup}
(async () => {
try {
const pkg = require(${JSON.stringify(packageName)});
// Find the tool export - check named export, default.name, or default
let tool = pkg[${JSON.stringify(name)}] || pkg.default?.[${JSON.stringify(name)}] || pkg.default;
if (!tool) {
throw new Error(\`Tool "${name}" not found in package "${packageName}"\`);
}
// Handle factory functions (tools that need to be instantiated)
if (typeof tool === 'function' && !tool.execute) {
const envVars = ${env ? JSON.stringify(env) : 'null'};
// Try no-arg call first
try {
const result = tool();
if (result && typeof result.execute === 'function') {
tool = result;
}
} catch {}
// Try with env config if still a function
if (typeof tool === 'function' && envVars) {
try {
const result = tool(envVars);
if (result && typeof result.execute === 'function') {
tool = result;
}
} catch {}
}
}
if (!tool || typeof tool.execute !== 'function') {
throw new Error(\`Tool "${name}" does not have an execute() function\`);
}
// Execute the tool
const result = await tool.execute(${JSON.stringify(params)});
process.stdout.write(JSON.stringify({ __tpmjs_result__: result }));
} catch (err) {
process.stderr.write(JSON.stringify({ __tpmjs_error__: err.message || String(err) }));
process.exitCode = 1;
}
})();
`.trim();
fs.writeFileSync(path.join(workDir, 'execute.cjs'), script);
// Run the execution script
console.log(`[executor] Running tool ${packageName}/${name}...`);
const runStart = Date.now();
const result = await new Promise((resolve) => {
const child = spawn('node', ['execute.cjs'], {
cwd: workDir,
env: { ...process.env, ...env },
timeout: 120000, // 2 minute timeout
});
let stdout = '';
let stderr = '';
child.stdout.on('data', (data) => (stdout += data));
child.stderr.on('data', (data) => (stderr += data));
child.on('close', (code) => {
resolve({ exitCode: code, stdout, stderr });
});
child.on('error', (err) => {
resolve({ exitCode: 1, stdout: '', stderr: err.message });
});
});
console.log(
`[executor] Tool execution completed in ${Date.now() - runStart}ms (exit: ${result.exitCode})`
);
// Cleanup work directory
try {
fs.rmSync(workDir, { recursive: true, force: true });
} catch {}
// Handle execution failure
if (result.exitCode !== 0) {
// Try to parse structured error from stderr
try {
const errorObj = JSON.parse(result.stderr);
if (errorObj.__tpmjs_error__) {
return jsonResponse(res, 200, {
success: false,
error: errorObj.__tpmjs_error__,
executionTimeMs: Date.now() - startTime,
});
}
} catch {}
return jsonResponse(res, 200, {
success: false,
error: result.stderr || `Script exited with code ${result.exitCode}`,
executionTimeMs: Date.now() - startTime,
});
}
// Parse the result
try {
const parsed = JSON.parse(result.stdout);
if (parsed.__tpmjs_result__ !== undefined) {
return jsonResponse(res, 200, {
success: true,
output: parsed.__tpmjs_result__,
executionTimeMs: Date.now() - startTime,
});
}
} catch {}
// If we couldn't parse structured output, return raw
return jsonResponse(res, 200, {
success: true,
output: result.stdout || null,
stderr: result.stderr || undefined,
executionTimeMs: Date.now() - startTime,
});
} catch (error) {
// Cleanup on error
try {
fs.rmSync(workDir, { recursive: true, force: true });
} catch {}
return jsonResponse(res, 500, {
success: false,
error: error.message || String(error),
executionTimeMs: Date.now() - startTime,
});
}
}
/**
* Main HTTP server
*/
const server = http.createServer(async (req, res) => {
const url = new URL(req.url, `http://localhost:${PORT}`);
const pathname = url.pathname;
// Handle CORS preflight
if (req.method === 'OPTIONS') {
res.writeHead(200, corsHeaders);
return res.end();
}
// Route requests (support both /api/path and /path)
if ((pathname === '/api/health' || pathname === '/health') && req.method === 'GET') {
return handleHealth(req, res);
}
if ((pathname === '/api/execute-tool' || pathname === '/execute-tool') && req.method === 'POST') {
return handleExecuteTool(req, res);
}
// 404 for unknown routes
jsonResponse(res, 404, { error: 'Not found' });
});
// Start server
server.listen(PORT, () => {
console.log(`TPMJS Executor running on port ${PORT}`);
console.log(`Health: http://localhost:${PORT}/api/health`);
console.log(`Execute: POST http://localhost:${PORT}/api/execute-tool`);
if (API_KEY) {
console.log(`Authentication: Required (EXECUTOR_API_KEY is set)`);
} else {
console.log(`Authentication: None (set EXECUTOR_API_KEY to enable)`);
}
});