원클릭으로
add-sandbox-adapter
// 为 agent-sandbox-adaptor 项目添加新的沙盒提供商适配器。当用户想要添加新的沙盒提供商支持、提到添加适配器、或提供沙盒服务文档时使用此技能。触发短语包括"添加 X 适配器"、"集成 X 沙盒"、"支持 X 提供商",或当用户分享沙盒服务的 API 文档时。
// 为 agent-sandbox-adaptor 项目添加新的沙盒提供商适配器。当用户想要添加新的沙盒提供商支持、提到添加适配器、或提供沙盒服务文档时使用此技能。触发短语包括"添加 X 适配器"、"集成 X 沙盒"、"支持 X 提供商",或当用户分享沙盒服务的 API 文档时。
| name | add-sandbox-adapter |
| description | 为 agent-sandbox-adaptor 项目添加新的沙盒提供商适配器。当用户想要添加新的沙盒提供商支持、提到添加适配器、或提供沙盒服务文档时使用此技能。触发短语包括"添加 X 适配器"、"集成 X 沙盒"、"支持 X 提供商",或当用户分享沙盒服务的 API 文档时。 |
此技能帮助你为 agent-sandbox-adaptor 项目添加新的沙盒提供商适配器。它自动化创建适配器代码、类型定义、环境变量、测试和集成更新。
在以下情况使用此技能:
agent-sandbox-adaptor 项目为不同的沙盒提供商提供统一接口。每个适配器:
createSandbox() 函数和类型联合中src/adapters/BaseSandboxAdapter.ts - 带有 polyfill 实现的基类src/adapters/OpenSandboxAdapter/ - 功能完整的参考实现src/adapters/SealosDevboxAdapter/ - 带自定义 API 的简单参考实现src/adapters/index.ts - 工厂函数和类型定义.env.test.template - 测试用环境变量模板向用户询问:
基于文档,确定:
在 src/adapters/{ProviderName}Adapter/ 中创建以下文件:
type.ts)定义 TypeScript 类型:
示例结构:
/**
* {Provider} 适配器配置
*/
export interface {Provider}Config {
/** API 端点 URL */
baseUrl: string;
/** 认证 token/key */
apiKey: string;
/** 沙盒标识符 */
sandboxId: string;
/** 可选的超时设置 */
timeout?: number;
}
/**
* 提供商特定的沙盒状态枚举
*/
export enum {Provider}StateEnum {
Running = 'running',
Pending = 'pending',
Stopped = 'stopped',
// ... 其他状态
}
/**
* API 响应类型
*/
export interface {Provider}InfoResponse {
id: string;
state: {Provider}StateEnum;
// ... 其他字段
}
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();
}
// 添加其他 API 方法:create、delete、execute 等
}
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;
// 为文件系统/搜索/健康检查操作初始化 polyfill 服务
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;
}
}
}
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;
}
// 在 createSandbox 函数中添加 case
export function createSandbox<P extends SandboxProviderType>(
provider: P,
config: SandboxConnectionConfig[P],
createConfig?: SandboxConfigMap[P]
): ISandbox {
switch (provider) {
// ... 现有的 case
case '{providername}':
return new {Provider}Adapter(config as {Provider}Config);
default:
throw new Error(`未知提供商: ${provider}`);
}
}
.env.test.template为新提供商添加环境变量:
# {Provider} 沙盒配置
{PROVIDER}_SANDBOX_BASE_URL=
{PROVIDER}_SANDBOX_API_KEY=
{PROVIDER}_SANDBOX_ID=
创建 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');
});
});
});
创建所有文件后:
pnpm typecheckpnpm testpnpm buildBaseSandboxAdapter 为以下功能提供 polyfill 实现:
这些使用 CommandPolyfillService 执行 shell 命令。在构造函数中初始化它:
this.polyfillService = new CommandPolyfillService(this);
当提供商直接支持时,原生实现方法:
仅当提供商有更好的原生实现时才覆盖 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 中。
对于具有简单 REST API 的提供商:
示例:SealosDevboxAdapter
对于具有官方 SDK 的提供商:
示例:OpenSandboxAdapter
对于 API 最少的提供商:
创建验证以下内容的测试:
不要在单元测试中测试实际的 API 调用 - 使用 mock 或集成测试。
完成前验证:
src/adapters/{Provider}Adapter/index.ts 中创建适配器类src/adapters/{Provider}Adapter/type.ts 中创建类型定义src/adapters/{Provider}Adapter/api.ts 中创建 API 客户端(如果需要)src/adapters/index.ts.env.test.templatetests/unit/adapters/{Provider}Adapter.test.ts 中创建单元测试用户:"添加 E2B 沙盒的适配器"
你:
BaseSandboxAdapter 的 E2BAdapter 类用户:"这是 Modal API 文档:[提供链接或文件]"
你: