cerevox
Version:
TypeScript SDK for browser automation and secure command execution in highly available and scalable micro computer environments
327 lines • 13.7 kB
JavaScript
"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