Skip to main content Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Un comando directo omite el prompt de revisión. Revisa el origen antes de ejecutarlo.
npx skills add https://github.com/labring/agent-sandbox-adaptor --skill add-sandbox-adapterEl comando permanece en una sola línea. Desplázate horizontalmente para revisarlo antes de copiarlo.
¿Prefieres una copia local? Descarga los archivos que SkillsMP tiene disponibles ahora.
Ocupaciones relacionadasSOC
Basado en la clasificación ocupacional SOC
Explorador de archivos
3 archivos| name | add-sandbox-adapter |
| description | 为 agent-sandbox-adaptor 项目添加新的沙盒提供商适配器。当用户想要添加新的沙盒提供商支持、提到添加适配器、或提供沙盒服务文档时使用此技能。触发短语包括"添加 X 适配器"、"集成 X 沙盒"、"支持 X 提供商",或当用户分享沙盒服务的 API 文档时。 |
添加沙盒适配器技能
此技能帮助你为 agent-sandbox-adaptor 项目添加新的沙盒提供商适配器。它自动化创建适配器代码、类型定义、环境变量、测试和集成更新。
何时使用此技能
在以下情况使用此技能:
- 用户想要添加新的沙盒提供商支持
- 用户提到"添加适配器"、"新提供商"、"集成沙盒"
- 用户提供沙盒服务的 API 文档或 SDK
- 用户指定沙盒厂商名称(例如:"添加 E2B 适配器"、"支持 Modal")
理解项目结构
agent-sandbox-adaptor 项目为不同的沙盒提供商提供统一接口。每个适配器:
- 继承 BaseSandboxAdapter - 继承通用功能和 polyfill 服务
- 实现 ISandbox 接口 - 提供生命周期、命令执行、文件系统和健康检查方法
- 拥有独立目录 - 包含适配器实现、类型定义和可选的 API 客户端
- 在工厂中注册 - 添加到
createSandbox() 函数和类型联合中
需要理解的关键文件
src/adapters/BaseSandboxAdapter.ts - 带有 polyfill 实现的基类
src/adapters/OpenSandboxAdapter/ - 功能完整的参考实现
src/adapters/SealosDevboxAdapter/ - 带自定义 API 的简单参考实现
src/adapters/index.ts - 工厂函数和类型定义
.env.test.template - 测试用环境变量模板
工作流程
步骤 1:收集信息
向用户询问:
- 提供商名称 - 这个适配器应该叫什么?(例如:"e2b"、"modal"、"runpod")
- 文档 - API 文档、SDK 链接或文档文件
- 认证方式 - 提供商如何认证?(API key、token、凭证)
- 核心能力 - 提供商支持哪些功能?
- 原生命令执行还是需要 polyfill?
- 原生文件系统操作还是需要 polyfill?
- 生命周期操作(创建、启动、停止、删除、暂停/恢复)?
- 后台执行支持?
- 指标和健康检查?
步骤 2:分析提供商
基于文档,确定:
SDK 可用性 - 提供商是否有官方的 TypeScript/JavaScript SDK?API 结构 - REST API、gRPC、WebSocket 还是其他?认证方法 - header 中的 API key、token、OAuth 等资源模型 - 沙盒如何创建和管理?命令执行 - 同步、流式还是两者都支持?功能支持 - 哪些 ISandbox 方法可以原生实现,哪些需要 polyfill?步骤 3:创建适配器结构
在 src/adapters/{ProviderName}Adapter/ 中创建以下文件:
3.1 类型定义 (type.ts)
- 提供商特定的配置
- API 请求/响应类型
- 状态/状态映射
- 资源规格
export interface {Provider}Config {
baseUrl: string;
apiKey: string;
sandboxId: string;
timeout?: number;
}
export enum {Provider}StateEnum {
Running = 'running',
Pending = 'pending',
Stopped = 'stopped',
}
export interface {Provider}InfoResponse {
id: string;
state: {Provider}StateEnum;
}
3.2 API 客户端(如果需要)(api.ts)
如果提供商没有官方 SDK,创建简单的 API 客户端:
import type { {Provider}Config, {Provider}InfoResponse } from './type';
export class {Provider}Api {
private baseUrl: string;
private apiKey: string;
constructor(config: {Provider}Config) {
this.baseUrl = config.baseUrl;
this.apiKey = config.apiKey;
}
async getInfo(sandboxId: string): Promise<{Provider}InfoResponse> {
const response = await fetch(`${this.baseUrl}/sandboxes/${sandboxId}`, {
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`API 请求失败: ${response.statusText}`);
}
return response.json();
}
}
3.3 适配器实现 (index.ts)
import { BaseSandboxAdapter } from '../BaseSandboxAdapter';
import { CommandPolyfillService } from '@/polyfill/CommandPolyfillService';
import { CommandExecutionError, ConnectionError } from '@/errors';
import type {
ExecuteOptions,
ExecuteResult,
SandboxId,
SandboxInfo,
SandboxState
} from '@/types';
import { {Provider}Api } from './api';
import type { {Provider}Config, {Provider}StateEnum } from './type';
export class {Provider}Adapter extends BaseSandboxAdapter {
readonly provider = '{providername}' as const;
private api: {Provider}Api;
private _id: SandboxId;
constructor(private config: {Provider}Config) {
super();
this.api = new {Provider}Api(config);
this._id = config.sandboxId;
this.polyfillService = new CommandPolyfillService(this);
}
get id(): SandboxId {
return this._id;
}
private mapState(providerState: {Provider}StateEnum): SandboxState {
switch (providerState) {
case {Provider}StateEnum.Running:
return 'Running';
case {Provider}StateEnum.Pending:
return 'Creating';
case {Provider}StateEnum.Stopped:
return 'Stopped';
default:
return 'Error';
}
}
async ensureRunning(): Promise<void> {
const info = await this.getInfo();
if (!info) {
await this.create();
return;
}
const state = info.status.state;
switch (state) {
case 'Running':
return;
case 'Creating':
case 'Starting':
await this.waitUntilReady();
return;
case 'Stopped':
await this.start();
return;
default:
throw new ConnectionError('沙盒处于意外状态');
}
}
async create(): Promise<void> {
try {
this._status = { state: 'Creating' };
await this.api.create(this._id);
await this.waitUntilReady();
this._status = { state: 'Running' };
} catch (error) {
this._status = { state: 'Error', message: String(error) };
throw new ConnectionError('创建沙盒失败', this.config.baseUrl, error);
}
}
async start(): Promise<void> {
try {
this._status = { state: 'Starting' };
await this.api.start(this._id);
await this.waitUntilReady();
this._status = { state: 'Running' };
} catch (error) {
throw new CommandExecutionError(
'启动沙盒失败',
'start',
error instanceof Error ? error : undefined
);
}
}
async stop(): Promise<void> {
try {
this._status = { state: 'Stopping' };
await this.api.stop(this._id);
this._status = { state: 'Stopped' };
} catch (error) {
throw new CommandExecutionError(
'停止沙盒失败',
'stop',
error instanceof Error ? error : undefined
);
}
}
async delete(): Promise<void> {
try {
this._status = { state: 'Deleting' };
await this.api.delete(this._id);
this._status = { state: 'UnExist' };
} catch (error) {
throw new CommandExecutionError(
'删除沙盒失败',
'delete',
error instanceof Error ? error : undefined
);
}
}
async getInfo(): Promise<SandboxInfo | null> {
try {
const info = await this.api.getInfo(this._id);
this._status = { state: this.mapState(info.state) };
return {
id: info.id,
image: { repository: info.image || '' },
entrypoint: [],
status: this._status,
createdAt: new Date(info.createdAt)
};
} catch (error) {
return null;
}
}
async execute(command: string, options?: ExecuteOptions): Promise<ExecuteResult> {
const cmd = this.buildCommand(command, options?.workingDirectory);
try {
const result = await this.api.execute(this._id, {
command: cmd,
timeout: options?.timeoutMs
});
return {
stdout: result.stdout || '',
stderr: result.stderr || '',
exitCode: result.exitCode || 0
};
} catch (error) {
throw new CommandExecutionError(
`命令执行失败: ${error}`,
command,
error instanceof Error ? error : undefined
);
}
}
async ping(): Promise<boolean> {
try {
const info = await this.api.getInfo(this._id);
return info.state === {Provider}StateEnum.Running;
} catch {
return false;
}
}
}
步骤 4:更新集成文件
4.1 更新 src/adapters/index.ts
import { {Provider}Adapter, type {Provider}Config } from './{Provider}Adapter';
export { {Provider}Adapter } from './{Provider}Adapter';
export type { {Provider}Config } from './{Provider}Adapter';
export type SandboxProviderType = 'opensandbox' | 'sealosdevbox' | '{providername}';
interface SandboxConfigMap {
opensandbox: OpenSandboxConfigType;
sealosdevbox: undefined;
{providername}: undefined;
}
interface SandboxConnectionConfig {
opensandbox: OpenSandboxConnectionConfig;
sealosdevbox: SealosDevboxConfig;
{providername}: {Provider}Config;
}
export function createSandbox<P extends SandboxProviderType>(
provider: P,
config: SandboxConnectionConfig[P],
createConfig?: SandboxConfigMap[P]
): ISandbox {
switch (provider) {
case '{providername}':
return new {Provider}Adapter(config as {Provider}Config);
default:
throw new Error(`未知提供商: ${provider}`);
}
}
4.2 更新 .env.test.template
{PROVIDER}_SANDBOX_BASE_URL=
{PROVIDER}_SANDBOX_API_KEY=
{PROVIDER}_SANDBOX_ID=
步骤 5:创建单元测试
创建 tests/unit/adapters/{Provider}Adapter.test.ts:
import { describe, expect, it } from 'vitest';
import { {Provider}Adapter } from '@/adapters/{Provider}Adapter';
import { ConnectionError, SandboxStateError } from '@/errors';
import type { {Provider}Config } from '@/adapters/{Provider}Adapter/type';
describe('{Provider}Adapter', () => {
describe('初始化', () => {
it('应该使用正确的提供商名称初始化', () => {
const config: {Provider}Config = {
baseUrl: 'https://api.example.com',
apiKey: 'test-key',
sandboxId: 'test-sandbox'
};
const adapter = new {Provider}Adapter(config);
expect(adapter.provider).toBe('{providername}');
expect(adapter.id).toBe('test-sandbox');
expect(adapter.status.state).toBe('Creating');
});
});
describe('生命周期方法', () => {
it('当沙盒不存在时应该处理 getInfo', async () => {
const config: {Provider}Config = {
baseUrl: 'https://api.example.com',
apiKey: 'invalid-key',
sandboxId: 'non-existent'
};
const adapter = new {Provider}Adapter(config);
const info = await adapter.getInfo();
expect(info).toBeNull();
});
it('应该正确跟踪状态', () => {
const config: {Provider}Config = {
baseUrl: 'https://api.example.com',
apiKey: 'test-key',
sandboxId: 'test-sandbox'
};
const adapter = new {Provider}Adapter(config);
expect(adapter.status.state).toBe('Creating');
const validStates = [
'UnExist',
'Running',
'Creating',
'Starting',
'Stopping',
'Stopped',
'Deleting',
'Error'
];
expect(validStates).toContain(adapter.status.state);
});
});
describe('错误处理', () => {
it('应该提供有意义的错误消息', () => {
const connectionError = new ConnectionError(
'创建沙盒失败',
'https://api.example.com',
new Error('网络超时')
);
expect(connectionError.message).toContain('创建沙盒失败');
expect(connectionError.endpoint).toBe('https://api.example.com');
expect(connectionError.cause).toBeDefined();
});
});
describe('提供商', () => {
it('应该有唯一的提供商名称', () => {
const config: {Provider}Config = {
baseUrl: 'https://api.example.com',
apiKey: 'test-key',
sandboxId: 'test-sandbox'
};
const adapter = new {Provider}Adapter(config);
expect(adapter.provider).toBe('{providername}');
expect(adapter.provider).not.toBe('opensandbox');
expect(adapter.provider).not.toBe('sealosdevbox');
});
});
});
步骤 6:验证集成
- 运行类型检查:
pnpm typecheck
- 运行测试:
pnpm test
- 构建项目:
pnpm build
- 验证导出:检查新适配器是否正确导出
实现指南
使用 Polyfill 服务
BaseSandboxAdapter 为以下功能提供 polyfill 实现:
- 文件系统操作(readFiles、writeFiles、deleteFiles、moveFiles)
- 目录操作(createDirectories、deleteDirectories、listDirectory)
- 搜索操作(search)
- 健康检查(ping、getMetrics)
这些使用 CommandPolyfillService 执行 shell 命令。在构造函数中初始化它:
this.polyfillService = new CommandPolyfillService(this);
原生 vs Polyfill
- OpenSandbox 有原生的文件系统、指标和流式支持
- SealosDevbox 对大多数操作使用 polyfill
仅当提供商有更好的原生实现时才覆盖 polyfill 方法。
错误处理
ConnectionError - 用于连接和初始化失败
CommandExecutionError - 用于命令执行失败
SandboxStateError - 用于无效的状态转换
FeatureNotSupportedError - 用于不支持的功能
TimeoutError - 用于超时场景
状态映射
将提供商特定的状态映射到标准 SandboxState 枚举:
'UnExist' - 沙盒不存在
'Creating' - 正在创建
'Starting' - 正在启动
'Running' - 准备好执行命令
'Stopping' - 正在关闭
'Stopped' - 已暂停/停止
'Deleting' - 正在删除
'Error' - 错误状态
命令构建
使用继承的 buildCommand() 方法处理工作目录:
const cmd = this.buildCommand(command, options?.workingDirectory);
这会正确转义路径并将命令包装在 sh -lc 中。
常见模式
模式 1:简单 REST API 提供商
- 创建带有 fetch 调用的 API 客户端类
- 将 API 响应映射到 ISandbox 类型
- 对文件系统操作使用 polyfill
- 实现生命周期和执行方法
模式 2:基于 SDK 的提供商
- 直接导入和使用 SDK
- 在 SDK 提供的地方实现原生方法
- 仅对不支持的功能使用 polyfill
- 处理 SDK 特定的错误类型
模式 3:功能有限的提供商
- 仅实现支持的生命周期方法
- 对不支持的功能抛出 FeatureNotSupportedError
- 大量依赖 polyfill 服务
- 清楚地记录限制
测试策略
- 初始化 - 正确的提供商名称、初始状态
- 配置 - 配置验证和默认值
- 状态管理 - 通过生命周期跟踪状态
- 错误处理 - 正确的错误类型和消息
- 类型安全 - TypeScript 类型正确
不要在单元测试中测试实际的 API 调用 - 使用 mock 或集成测试。
检查清单
示例交互
- 询问 E2B API 文档或 SDK 信息
- 分析 E2B API 结构和能力
- 创建继承
BaseSandboxAdapter 的 E2BAdapter 类
- 为 E2B 特定类型创建类型定义
- 如果没有 SDK,创建 API 客户端
- 更新工厂函数和类型联合
- 将环境变量添加到模板
- 生成单元测试
- 验证一切编译且测试通过
用户:"这是 Modal API 文档:[提供链接或文件]"
- 阅读和分析 Modal 文档
- 识别认证方法、API 端点和能力
- 创建完整的适配器实现
- 生成所有支持文件
- 集成到项目中
- 创建全面的测试
- 验证实现
提示
- 对于 REST API 提供商,从更简单的 SealosDevboxAdapter 作为模板开始
- 对于基于 SDK 的提供商,使用 OpenSandboxAdapter 作为参考
- 除非提供商对所有内容都有原生实现,否则始终初始化 polyfillService
- 保持提供商名称小写且简洁
- 记录任何提供商特定的限制或怪癖
- 测试错误场景,而不仅仅是正常路径