UNPKG

cerevox

Version:

TypeScript SDK for browser automation and secure command execution in highly available and scalable micro computer environments

327 lines 13.7 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var Terminal_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.Terminal = void 0; const base_1 = require("./base"); const constants_1 = require("../utils/constants"); const node_stream_1 = require("node:stream"); const uuid_1 = require("uuid"); const session_1 = require("./session"); /** * Terminal 类 - 提供沙箱环境中的终端命令执行功能 * * 主要功能: * - 在沙箱环境中执行命令 * - 管理工作目录状态 * - 提供流式输出处理 * - 支持命令终止和控制 * - 环境变量管理 * * @example * ```typescript * import Sandbox from 'e2b'; * import { Terminal } from './terminal'; * * const sandbox = new Sandbox(); * const terminal = new Terminal(sandbox); * * // 执行命令 * const result = await terminal.run('ls -la'); * result.stdout.on('data', (chunk) => { * console.log(chunk.toString()); * }); * * // 等待命令完成 * await result.end(); * ``` */ let Terminal = Terminal_1 = class Terminal extends base_1.BaseClass { /** * 创建 Terminal 实例 * @param sandbox - E2B 沙箱实例 * @param envs - 环境变量配置 * @param id - 终端唯一标识符 * @param logLevel - 日志级别 */ constructor(session, options = {}) { super(session.getLogger().level); this.session = session; /** 当前工作目录 */ this.cwd = ''; /** 环境变量 */ this.envs = {}; this.id = options.id || 'default0'; this.envs = options.envs || {}; this.logger.debug('Terminal 实例已创建', { id: this.id, envCount: Object.keys(this.envs).length, }); } /** * 获取当前工作目录 * @returns 当前工作目录路径 */ async getCwd() { const cwd = this.cwd || (await this.updateCwd()); this.logger.debug('获取当前工作目录', { cwd }); return cwd; } /** * 更新当前工作目录 * @private * @returns 更新后的工作目录路径 */ async updateCwd() { try { this.cwd = (await this.session.files.read(`/home/user/.pwd.${this.id}`)).trim(); this.logger.debug('工作目录已更新', { cwd: this.cwd, id: this.id }); } catch (ex) { this.cwd = '/home/user'; this.logger.debug('使用默认工作目录', { cwd: this.cwd, reason: '无法读取保存的路径', }); } return this.cwd; } /** * 创建新的 Terminal 实例 * @returns 新的 Terminal 实例,具有唯一的 ID */ create() { const newId = (0, uuid_1.v4)(); this.logger.debug('创建新的 Terminal 实例', { newId, parentId: this.id }); return new Terminal_1(this.session, { envs: this.envs, id: newId, }); } /** * 通过 API 执行命令 * @param command - 要执行的命令 * @param opts - 命令执行选项 * @returns 命令响应对象 */ async run(command, opts) { try { this.logger.debug('通过 API 执行命令', { command, terminalId: this.id, options: opts, }); const cwd = await this.getCwd(); const stdoutStream = new node_stream_1.Readable({ read() { }, }); const stderrStream = new node_stream_1.Readable({ read() { }, }); let commandResult = null; let isFinished = false; let isKilled = false; let finishResolve = null; let error = ''; const finishPromise = new Promise(resolve => { finishResolve = resolve; }); // 用于终止请求的 AbortController const abortController = new AbortController(); // 先发送 POST 请求启动命令 const response = await this.session.sandbox.request(`/api/terminal/run`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ command: `${command} && pwd > ~/.pwd.${this.id}`, cwd: opts?.cwd || cwd, timeout: opts?.timeoutMs, }), signal: abortController.signal, }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } // 处理 SSE 流 if (!response.body) { throw new Error('无法获取响应流'); } const processStream = async () => { try { let buffer = ''; const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value, { stream: true }); buffer += chunk; const messages = buffer.split('\n\n'); // 保留最后一个消息(可能不完整) buffer = messages.pop() || ''; for (const message of messages) { if (!message.trim()) continue; const lines = message.split('\n'); let eventType = ''; let eventData = ''; for (const line of lines) { const trimmedLine = line.trim(); if (trimmedLine.startsWith('event:')) { eventType = trimmedLine.substring(6).trim(); } else if (trimmedLine.startsWith('data:')) { eventData = trimmedLine.substring(5).trim(); } } if (eventType && eventData) { try { const data = JSON.parse(eventData); switch (eventType) { case 'start': this.logger.debug('命令开始执行', { terminalId: this.id, data, }); break; case 'stdout': stdoutStream.push(data.output); break; case 'stderr': stderrStream.push(data.output); break; case 'finish': commandResult = { exitCode: data.exitCode, stdout: data.stdout, stderr: data.stderr, error, }; isFinished = true; stdoutStream.push(null); stderrStream.push(null); if (finishResolve) { finishResolve(); } this.logger.debug('命令执行完成', { terminalId: this.id, exitCode: data.exitCode, success: data.success, }); return; // 结束流处理 case 'timeout': this.logger.warn('命令执行超时', { terminalId: this.id, data, }); error = 'timeout'; break; case 'error': this.logger.error('命令执行错误', { terminalId: this.id, error: data.error, }); error = data.error; break; } } catch (parseError) { this.logger.error('解析 SSE 数据失败', { terminalId: this.id, error: parseError, rawData: eventData, }); } } } } // 流结束处理 if (!isFinished && !isKilled) { stdoutStream.push(null); stderrStream.push(null); if (finishResolve) { finishResolve(); } } } catch (error) { if (!isKilled) { this.logger.error('SSE 流处理错误', { terminalId: this.id, error, }); } if (!isFinished && !isKilled) { stdoutStream.push(null); stderrStream.push(null); if (finishResolve) { finishResolve(); } } } }; // 开始处理流 processStream(); return { stdout: stdoutStream, stderr: stderrStream, end: async () => { this.logger.debug('等待命令完成', { terminalId: this.id }); await finishPromise; await this.updateCwd(); this.logger.debug('命令执行完成', { terminalId: this.id }); }, kill: async () => { this.logger.debug('终止命令执行', { terminalId: this.id }); isKilled = true; abortController.abort(); stdoutStream.push(null); stderrStream.push(null); if (finishResolve) { finishResolve(); } this.logger.debug('命令已被终止', { terminalId: this.id }); }, json: async () => { this.logger.debug('获取命令执行结果', { terminalId: this.id }); await finishPromise; await this.updateCwd(); if (!commandResult) { throw new Error('命令执行结果不可用'); } this.logger.debug('命令执行结果', { terminalId: this.id, exitCode: commandResult.exitCode, success: commandResult.exitCode === 0, }); return commandResult; }, }; } catch (ex) { this.logger.error('exec 方法执行失败', { command, terminalId: this.id, error: ex, }); throw ex; } } }; exports.Terminal = Terminal; exports.Terminal = Terminal = Terminal_1 = __decorate([ (0, base_1.Logger)({ VERSION: constants_1.VERSION }), __metadata("design:paramtypes", [session_1.Session, Object]) ], Terminal); //# sourceMappingURL=terminal.js.map