import { request as httpRequest, createServer } from 'node:http'; import { createInterface } from 'node:readline'; import { spawn } from 'node:child_process'; // Runs INSIDE the OS sandbox. Its only privileged connection is the run-scoped // broker socket; it never receives database/storage/provider credentials. function forward(path, body, headers = {}) { return new Promise((resolve, reject) => { const endpoint = process.env.CANVAS_AGENT_BROKER_URL; const target = endpoint ? new URL(path, endpoint) : null; const request = httpRequest({ ...(target ? { hostname: target.hostname, port: target.port, path: target.pathname } : { socketPath: '/run/broker.sock', path }), method: 'POST', headers: { 'content-type': 'application/json', ...headers, authorization: `Bearer ${process.env.CANVAS_AGENT_RUN_TOKEN}` }, }, resolve); request.on('error', reject); request.setTimeout(20 * 60_000, () => request.destroy(new Error('任务网关超时'))); request.end(body); }); } if (process.argv[2] === 'mcp') { const lines = createInterface({ input: process.stdin }); const outstanding = new Set(); for await (const line of lines) { if (!line.trim()) continue; const job = (async () => { let message; try { message = JSON.parse(line); const response = await forward('/mcp', JSON.stringify(message)); const chunks = []; for await (const chunk of response) chunks.push(chunk); const result = JSON.parse(Buffer.concat(chunks).toString('utf8')); if (message.id !== undefined) process.stdout.write(`${JSON.stringify(result)}\n`); } catch { if (message?.id !== undefined) process.stdout.write(`${JSON.stringify({ jsonrpc: '2.0', id: message.id, error: { code: -32603, message: '画布工具调用中断;请核对任务记录,勿重复提交收费操作' } })}\n`); } })(); outstanding.add(job); job.finally(() => outstanding.delete(job)); } await Promise.allSettled(outstanding); } else if (process.argv[2] === 'launch') { // Linux has a private network namespace: only this loopback adapter exists. const server = createServer(async (request, response) => { try { if (!['/v1/chat/completions', '/v1/responses', '/v1/messages'].includes(request.url)) { response.writeHead(404); response.end(); return; } const chunks = []; let size = 0; for await (const chunk of request) { size += chunk.length; if (size > 32 * 1024 * 1024) throw new Error('oversize'); chunks.push(chunk); } const upstream = await forward(request.url, Buffer.concat(chunks)); response.writeHead(upstream.statusCode || 502, { 'content-type': upstream.headers['content-type'] || 'application/json' }); upstream.pipe(response); } catch { response.writeHead(502); response.end('{"error":{"message":"任务模型网关暂不可用"}}'); } }); await new Promise(resolve => server.listen(32119, '127.0.0.1', resolve)); const child = spawn(process.execPath, process.argv.slice(3), { stdio: 'inherit', shell: false }); for (const signal of ['SIGINT', 'SIGTERM']) process.on(signal, () => child.kill(signal)); child.on('error', () => { server.close(); process.exitCode = 1; }); child.on('close', code => { server.close(); process.exitCode = code ?? 1; }); } else throw new Error('无效的沙箱通信模式');