@fastgpt-sdk/sandbox-adapter 0.0.19 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,6 +10,24 @@ A unified, high-level abstraction layer for cloud sandbox providers. It offers a
10
10
  pnpm add @fastgpt/sandbox
11
11
  ```
12
12
 
13
+ ## Next.js 集成
14
+
15
+ 如果在 Next.js 项目中使用,需要配置以处理 ESM 依赖。在 `next.config.js` 中添加:
16
+
17
+ ```javascript
18
+ /** @type {import('next').NextConfig} */
19
+ const nextConfig = {
20
+ transpilePackages: ['@fastgpt-sdk/sandbox-adapter'],
21
+ experimental: {
22
+ esmExternals: 'loose',
23
+ },
24
+ };
25
+
26
+ module.exports = nextConfig;
27
+ ```
28
+
29
+ 详细说明请参考 [Next.js 集成指南](./docs/NEXTJS_INTEGRATION.md)。
30
+
13
31
  ## 用途
14
32
 
15
33
  ### 1. 操作沙盒
@@ -0,0 +1,38 @@
1
+ import { BaseSandboxAdapter } from '../BaseSandboxAdapter';
2
+ import type { ExecuteOptions, ExecuteResult, SandboxId, SandboxInfo, FileWriteEntry, FileWriteResult, FileDeleteResult, FileReadResult, MoveEntry, DirectoryEntry } from '@/types';
3
+ import type { E2BConfig } from './type';
4
+ /**
5
+ * E2B 沙盒适配器 - 使用官方 SDK
6
+ *
7
+ * 使用 metadata 映射上游 sandboxId 到 E2B 实际 ID
8
+ */
9
+ export declare class E2BAdapter extends BaseSandboxAdapter {
10
+ private config;
11
+ readonly provider: "e2b";
12
+ private sandbox;
13
+ private _id;
14
+ constructor(config: E2BConfig);
15
+ get id(): SandboxId;
16
+ /**
17
+ * 通过 metadata 查找 E2B 沙盒实例
18
+ * @returns E2B Sandbox 实例,如果未找到则返回 null
19
+ */
20
+ private findSandbox;
21
+ private ensureSandbox;
22
+ ensureRunning(): Promise<void>;
23
+ create(): Promise<void>;
24
+ start(): Promise<void>;
25
+ stop(): Promise<void>;
26
+ delete(): Promise<void>;
27
+ getInfo(): Promise<SandboxInfo | null>;
28
+ execute(command: string, options?: ExecuteOptions): Promise<ExecuteResult>;
29
+ readFiles(paths: string[]): Promise<FileReadResult[]>;
30
+ writeFiles(files: FileWriteEntry[]): Promise<FileWriteResult[]>;
31
+ deleteFiles(paths: string[]): Promise<FileDeleteResult[]>;
32
+ moveFiles(moves: MoveEntry[]): Promise<void>;
33
+ createDirectories(paths: string[]): Promise<void>;
34
+ deleteDirectories(paths: string[]): Promise<void>;
35
+ listDirectory(path: string): Promise<DirectoryEntry[]>;
36
+ ping(): Promise<boolean>;
37
+ }
38
+ export type { E2BConfig } from './type';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * E2B 适配器配置
3
+ */
4
+ export interface E2BConfig {
5
+ /** E2B API Key */
6
+ apiKey: string;
7
+ /** 可选的沙盒 ID,用于连接到已存在的沙盒 */
8
+ sandboxId: string;
9
+ /** 可选的模板 ID,用于创建新沙盒 */
10
+ template?: string;
11
+ /** 可选的超时时间(秒) */
12
+ timeout?: number;
13
+ /** 可选的环境变量 */
14
+ envs?: Record<string, string>;
15
+ /** 可选的元数据 */
16
+ metadata?: Record<string, string>;
17
+ }
@@ -1,21 +1,26 @@
1
1
  import { type SealosDevboxConfig } from './SealosDevboxAdapter';
2
2
  import { type OpenSandboxConnectionConfig, type OpenSandboxConfigType } from './OpenSandboxAdapter';
3
+ import { type E2BConfig } from './E2BAdapter';
3
4
  import { ISandbox } from '@/interfaces';
4
5
  export { SealosDevboxAdapter } from './SealosDevboxAdapter';
5
6
  export type { SealosDevboxConfig } from './SealosDevboxAdapter';
6
7
  export { OpenSandboxAdapter } from './OpenSandboxAdapter';
7
8
  export type { OpenSandboxConfigType, OpenSandboxConnectionConfig } from './OpenSandboxAdapter';
8
- export type SandboxProviderType = 'opensandbox' | 'sealosdevbox';
9
+ export { E2BAdapter } from './E2BAdapter';
10
+ export type { E2BConfig } from './E2BAdapter';
11
+ export type SandboxProviderType = 'opensandbox' | 'sealosdevbox' | 'e2b';
9
12
  /** Maps each provider name to the ISandbox config type it exposes. */
10
13
  interface SandboxConfigMap {
11
14
  opensandbox: OpenSandboxConfigType;
12
15
  sealosdevbox: undefined;
16
+ e2b: undefined;
13
17
  }
14
18
  /** Resolves the concrete ISandbox type for a given provider. */
15
19
  /** Maps each provider name to its constructor (connection) config type. */
16
20
  interface SandboxConnectionConfig {
17
21
  opensandbox: OpenSandboxConnectionConfig;
18
22
  sealosdevbox: SealosDevboxConfig;
23
+ e2b: E2BConfig;
19
24
  }
20
25
  /**
21
26
  * Create a sandbox provider instance.
package/dist/index.js CHANGED
@@ -825,6 +825,7 @@ class SealosDevboxAdapter extends BaseSandboxAdapter {
825
825
  this._status = { state: "Creating" };
826
826
  await this.api.create(this._id);
827
827
  await this.waitUntilReady();
828
+ await new Promise((resolve) => setTimeout(resolve, 1000));
828
829
  this._status = { state: "Running" };
829
830
  } catch (error) {
830
831
  throw new ConnectionError("Failed to create sandbox", this.config.baseUrl, error);
@@ -861,7 +862,6 @@ class SealosDevboxAdapter extends BaseSandboxAdapter {
861
862
  }
862
863
  async execute(command, options) {
863
864
  const cmd = this.buildCommand(command, options?.workingDirectory);
864
- console.log(cmd, 2322222222222);
865
865
  try {
866
866
  const res = await this.api.exec(this._id, {
867
867
  command: cmd,
@@ -1278,6 +1278,318 @@ class OpenSandboxAdapter extends BaseSandboxAdapter {
1278
1278
  }
1279
1279
  }
1280
1280
 
1281
+ // src/adapters/E2BAdapter/index.ts
1282
+ import { Sandbox as Sandbox2, CommandExitError, FileType } from "@e2b/code-interpreter";
1283
+ class E2BAdapter extends BaseSandboxAdapter {
1284
+ config;
1285
+ provider = "e2b";
1286
+ sandbox = null;
1287
+ _id;
1288
+ constructor(config) {
1289
+ super();
1290
+ this.config = config;
1291
+ this._id = config.sandboxId;
1292
+ this.polyfillService = new CommandPolyfillService(this);
1293
+ }
1294
+ get id() {
1295
+ return this._id;
1296
+ }
1297
+ async findSandbox() {
1298
+ try {
1299
+ const paginator = Sandbox2.list({
1300
+ apiKey: this.config.apiKey,
1301
+ query: {
1302
+ metadata: { upstreamId: this._id }
1303
+ },
1304
+ limit: 1
1305
+ });
1306
+ const sandboxes = await paginator.nextItems();
1307
+ if (sandboxes.length > 0) {
1308
+ const sandboxInfo = sandboxes[0];
1309
+ const sandbox = await Sandbox2.connect(sandboxInfo.sandboxId, {
1310
+ apiKey: this.config.apiKey
1311
+ });
1312
+ return sandbox;
1313
+ }
1314
+ return null;
1315
+ } catch (error) {
1316
+ console.error("Failed to find sandbox by metadata:", error);
1317
+ return Promise.reject(new Error("Failed to find sandbox by metadata"));
1318
+ }
1319
+ }
1320
+ async ensureSandbox() {
1321
+ await this.ensureRunning();
1322
+ return this.sandbox;
1323
+ }
1324
+ async ensureRunning() {
1325
+ try {
1326
+ if (!this.sandbox) {
1327
+ this.sandbox = await this.findSandbox();
1328
+ }
1329
+ if (this.sandbox) {
1330
+ const isRunning = await this.sandbox.isRunning();
1331
+ if (isRunning) {
1332
+ this._status = { state: "Running" };
1333
+ return;
1334
+ }
1335
+ await Sandbox2.connect(this.sandbox.sandboxId, {
1336
+ apiKey: this.config.apiKey
1337
+ });
1338
+ await this.waitUntilReady();
1339
+ this._status = { state: "Running" };
1340
+ return;
1341
+ }
1342
+ await this.create();
1343
+ } catch (error) {
1344
+ throw new ConnectionError("Failed to ensure sandbox running", "e2b", error);
1345
+ }
1346
+ }
1347
+ async create() {
1348
+ try {
1349
+ this._status = { state: "Creating" };
1350
+ const options = {
1351
+ apiKey: this.config.apiKey,
1352
+ template: this.config.template,
1353
+ timeout: this.config.timeout,
1354
+ envs: this.config.envs,
1355
+ metadata: {
1356
+ ...this.config.metadata,
1357
+ upstreamId: this._id
1358
+ }
1359
+ };
1360
+ this.sandbox = await Sandbox2.create(options);
1361
+ await this.waitUntilReady();
1362
+ this._status = { state: "Running" };
1363
+ } catch (error) {
1364
+ this._status = { state: "Error", message: String(error) };
1365
+ throw new ConnectionError("Failed to create E2B sandbox", "e2b", error);
1366
+ }
1367
+ }
1368
+ async start() {
1369
+ await this.ensureSandbox();
1370
+ }
1371
+ async stop() {
1372
+ const sandbox = await this.ensureSandbox();
1373
+ try {
1374
+ this._status = { state: "Stopping" };
1375
+ await sandbox.pause({
1376
+ apiKey: this.config.apiKey
1377
+ });
1378
+ this._status = { state: "Stopped" };
1379
+ } catch (error) {
1380
+ throw new CommandExecutionError("Failed to pause E2B sandbox", "stop", error instanceof Error ? error : undefined);
1381
+ }
1382
+ }
1383
+ async delete() {
1384
+ const sandbox = await this.ensureSandbox();
1385
+ try {
1386
+ this._status = { state: "Deleting" };
1387
+ await sandbox.kill();
1388
+ this.sandbox = null;
1389
+ this._status = { state: "UnExist" };
1390
+ } catch (error) {
1391
+ throw new CommandExecutionError("Failed to kill E2B sandbox", "delete", error instanceof Error ? error : undefined);
1392
+ }
1393
+ }
1394
+ async getInfo() {
1395
+ try {
1396
+ const sandbox = await this.ensureSandbox();
1397
+ const isRunning = await sandbox.isRunning();
1398
+ return {
1399
+ id: this._id,
1400
+ image: { repository: this.config.template || "default" },
1401
+ entrypoint: [],
1402
+ status: {
1403
+ state: isRunning ? "Running" : "Stopped"
1404
+ },
1405
+ createdAt: new Date
1406
+ };
1407
+ } catch {
1408
+ return null;
1409
+ }
1410
+ }
1411
+ async execute(command, options) {
1412
+ try {
1413
+ const sandbox = await this.ensureSandbox();
1414
+ const result = await sandbox.commands.run(command, {
1415
+ cwd: options?.workingDirectory,
1416
+ timeoutMs: options?.timeoutMs
1417
+ });
1418
+ return {
1419
+ stdout: result.stdout,
1420
+ stderr: result.stderr,
1421
+ exitCode: result.exitCode
1422
+ };
1423
+ } catch (error) {
1424
+ if (error instanceof CommandExitError) {
1425
+ return {
1426
+ stdout: error.stdout ?? "",
1427
+ stderr: error.stderr ?? "",
1428
+ exitCode: error.exitCode ?? 1
1429
+ };
1430
+ }
1431
+ throw new CommandExecutionError(`Command execution failed: ${error}`, command, error instanceof Error ? error : undefined);
1432
+ }
1433
+ }
1434
+ async readFiles(paths) {
1435
+ const sandbox = await this.ensureSandbox();
1436
+ try {
1437
+ const results = [];
1438
+ for (const path of paths) {
1439
+ try {
1440
+ const content = await sandbox.files.read(path);
1441
+ results.push({
1442
+ path,
1443
+ content: Buffer.from(content),
1444
+ error: null
1445
+ });
1446
+ } catch (error) {
1447
+ results.push({
1448
+ path,
1449
+ content: new Uint8Array,
1450
+ error: error instanceof Error ? error : new Error(String(error))
1451
+ });
1452
+ }
1453
+ }
1454
+ return results;
1455
+ } catch (error) {
1456
+ throw new CommandExecutionError("Failed to read files", "readFiles", error instanceof Error ? error : undefined);
1457
+ }
1458
+ }
1459
+ async writeFiles(files) {
1460
+ const sandbox = await this.ensureSandbox();
1461
+ try {
1462
+ const results = [];
1463
+ const writeData = files.map((f) => {
1464
+ let data;
1465
+ if (typeof f.data === "string") {
1466
+ data = f.data;
1467
+ } else if (f.data instanceof Uint8Array) {
1468
+ data = f.data.buffer;
1469
+ } else if (f.data instanceof ArrayBuffer) {
1470
+ data = f.data;
1471
+ } else {
1472
+ data = "";
1473
+ }
1474
+ return {
1475
+ path: f.path,
1476
+ data
1477
+ };
1478
+ });
1479
+ try {
1480
+ await sandbox.files.write(writeData);
1481
+ for (const file of files) {
1482
+ let size = 0;
1483
+ if (typeof file.data === "string") {
1484
+ size = Buffer.byteLength(file.data);
1485
+ } else if (file.data instanceof ArrayBuffer) {
1486
+ size = file.data.byteLength;
1487
+ } else if (file.data instanceof Uint8Array) {
1488
+ size = file.data.byteLength;
1489
+ }
1490
+ results.push({
1491
+ path: file.path,
1492
+ bytesWritten: size,
1493
+ error: null
1494
+ });
1495
+ }
1496
+ } catch (error) {
1497
+ for (const file of files) {
1498
+ results.push({
1499
+ path: file.path,
1500
+ bytesWritten: 0,
1501
+ error: error instanceof Error ? error : new Error(String(error))
1502
+ });
1503
+ }
1504
+ }
1505
+ return results;
1506
+ } catch (error) {
1507
+ throw new CommandExecutionError("Failed to write files", "writeFiles", error instanceof Error ? error : undefined);
1508
+ }
1509
+ }
1510
+ async deleteFiles(paths) {
1511
+ const sandbox = await this.ensureSandbox();
1512
+ try {
1513
+ const results = [];
1514
+ for (const path of paths) {
1515
+ try {
1516
+ await sandbox.files.remove(path);
1517
+ results.push({
1518
+ path,
1519
+ success: true,
1520
+ error: null
1521
+ });
1522
+ } catch (error) {
1523
+ results.push({
1524
+ path,
1525
+ success: false,
1526
+ error: error instanceof Error ? error : new Error(String(error))
1527
+ });
1528
+ }
1529
+ }
1530
+ return results;
1531
+ } catch (error) {
1532
+ throw new CommandExecutionError("Failed to delete files", "deleteFiles", error instanceof Error ? error : undefined);
1533
+ }
1534
+ }
1535
+ async moveFiles(moves) {
1536
+ const sandbox = await this.ensureSandbox();
1537
+ try {
1538
+ for (const { source, destination } of moves) {
1539
+ await sandbox.files.rename(source, destination);
1540
+ }
1541
+ } catch (error) {
1542
+ throw new CommandExecutionError("Failed to move files", "moveFiles", error instanceof Error ? error : undefined);
1543
+ }
1544
+ }
1545
+ async createDirectories(paths) {
1546
+ const sandbox = await this.ensureSandbox();
1547
+ try {
1548
+ for (const path of paths) {
1549
+ await sandbox.files.makeDir(path);
1550
+ }
1551
+ } catch (error) {
1552
+ throw new CommandExecutionError("Failed to create directories", "createDirectories", error instanceof Error ? error : undefined);
1553
+ }
1554
+ }
1555
+ async deleteDirectories(paths) {
1556
+ const sandbox = await this.ensureSandbox();
1557
+ try {
1558
+ for (const path of paths) {
1559
+ await sandbox.files.remove(path);
1560
+ }
1561
+ } catch (error) {
1562
+ throw new CommandExecutionError("Failed to delete directories", "deleteDirectories", error instanceof Error ? error : undefined);
1563
+ }
1564
+ }
1565
+ async listDirectory(path) {
1566
+ const sandbox = await this.ensureSandbox();
1567
+ try {
1568
+ const entries = await sandbox.files.list(path);
1569
+ return entries.map((entry) => {
1570
+ const isDirectory = entry.type === FileType.DIR;
1571
+ return {
1572
+ name: entry.name,
1573
+ path: entry.path,
1574
+ isDirectory,
1575
+ isFile: !isDirectory,
1576
+ size: entry.size
1577
+ };
1578
+ });
1579
+ } catch (error) {
1580
+ throw new CommandExecutionError(`Failed to list directory: ${path}`, "listDirectory", error instanceof Error ? error : undefined);
1581
+ }
1582
+ }
1583
+ async ping() {
1584
+ try {
1585
+ const sandbox = await this.ensureSandbox();
1586
+ return await sandbox.isRunning();
1587
+ } catch {
1588
+ return false;
1589
+ }
1590
+ }
1591
+ }
1592
+
1281
1593
  // src/adapters/index.ts
1282
1594
  function createSandbox(provider, config, createConfig) {
1283
1595
  switch (provider) {
@@ -1285,6 +1597,8 @@ function createSandbox(provider, config, createConfig) {
1285
1597
  return new OpenSandboxAdapter(config, createConfig);
1286
1598
  case "sealosdevbox":
1287
1599
  return new SealosDevboxAdapter(config);
1600
+ case "e2b":
1601
+ return new E2BAdapter(config);
1288
1602
  default:
1289
1603
  throw new Error(`Unknown provider: ${provider}`);
1290
1604
  }
@@ -1299,6 +1613,7 @@ export {
1299
1613
  OpenSandboxAdapter,
1300
1614
  FileOperationError,
1301
1615
  FeatureNotSupportedError,
1616
+ E2BAdapter,
1302
1617
  ConnectionError,
1303
1618
  CommandExecutionError
1304
1619
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastgpt-sdk/sandbox-adapter",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "description": "Unified abstraction layer for cloud sandbox providers with adapter pattern and feature polyfilling",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -22,6 +22,20 @@
22
22
  "engines": {
23
23
  "node": ">=18"
24
24
  },
25
+ "scripts": {
26
+ "build": "bun run build.ts",
27
+ "dev": "tsc -p tsconfig.build.json --watch",
28
+ "prepublishOnly": "bun run build",
29
+ "lint": "eslint .",
30
+ "lint:fix": "eslint . --fix",
31
+ "format": "prettier . --write",
32
+ "format:check": "prettier . --check",
33
+ "lint-staged": "lint-staged",
34
+ "prepare": "husky",
35
+ "test": "vitest run --config ./vitest.config.mts",
36
+ "test:watch": "vitest watch",
37
+ "test:coverage": "vitest run --coverage"
38
+ },
25
39
  "keywords": [
26
40
  "sandbox",
27
41
  "cloud",
@@ -33,7 +47,8 @@
33
47
  "author": "",
34
48
  "license": "MIT",
35
49
  "dependencies": {
36
- "@alibaba-group/opensandbox": "^0.1.4"
50
+ "@alibaba-group/opensandbox": "^0.1.4",
51
+ "@e2b/code-interpreter": "^2.3.3"
37
52
  },
38
53
  "devDependencies": {
39
54
  "@eslint/js": "^9.39.2",
@@ -58,17 +73,5 @@
58
73
  "*.{js,cjs,mjs,ts,tsx}": [
59
74
  "eslint --max-warnings=0"
60
75
  ]
61
- },
62
- "scripts": {
63
- "build": "bun run build.ts",
64
- "dev": "tsc -p tsconfig.build.json --watch",
65
- "lint": "eslint .",
66
- "lint:fix": "eslint . --fix",
67
- "format": "prettier . --write",
68
- "format:check": "prettier . --check",
69
- "lint-staged": "lint-staged",
70
- "test": "vitest run --config ./vitest.config.mts",
71
- "test:watch": "vitest watch",
72
- "test:coverage": "vitest run --coverage"
73
76
  }
74
- }
77
+ }