@ computesdk/ 提供者
构建自定义计算提供者的提供者框架.
概览
这个软件包为计算SDK生态系统提供了自定义沙盒提供商的工具和类型. 使用它来添加对新计算平台,云提供商,或沙盒环境的支持.
安装
npm install @computesdk/provider
快速启动
import { defineProvider } from '@computesdk/provider';
import type { ProviderConfig } from '@computesdk/provider';
interface MyProviderConfig extends ProviderConfig {
apiKey: string;
region?: string;
}
export const myProvider = defineProvider<any, MyProviderConfig>({
name: 'my-provider',
defaultMode: 'direct',
methods: {
instances: {
create: async (config, options) => {
// Create sandbox via your provider's API
const response = await fetch(`https://api.example.com/sandboxes`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${config.apiKey}` },
body: JSON.stringify(options)
});
const data = await response.json();
return {
instance: data, // Your provider's sandbox object
instanceId: data.id
};
},
getById: async (config, instanceId) => {
const response = await fetch(
`https://api.example.com/sandboxes/${instanceId}`,
{ headers: { 'Authorization': `Bearer ${config.apiKey}` } }
);
if (!response.ok) return null;
const data = await response.json();
return { instance: data, instanceId: data.id };
},
list: async (config) => {
const response = await fetch(
`https://api.example.com/sandboxes`,
{ headers: { 'Authorization': `Bearer ${config.apiKey}` } }
);
const data = await response.json();
return data.map((s: any) => ({ instance: s, instanceId: s.id }));
},
destroy: async (config, instanceId) => {
await fetch(
`https://api.example.com/sandboxes/${instanceId}`,
{
method: 'DELETE',
headers: { 'Authorization': `Bearer ${config.apiKey}` }
}
);
},
runCommand: async (sandbox, command, args) => {
// Run shell command
const response = await fetch(
`https://api.example.com/sandboxes/${sandbox.id}/command`,
{
method: 'POST',
body: JSON.stringify({ command, args })
}
);
const result = await response.json();
return {
stdout: result.stdout,
stderr: result.stderr,
exitCode: result.exitCode
};
},
getInfo: async (sandbox) => {
return {
id: sandbox.id,
status: sandbox.status,
createdAt: new Date(sandbox.created_at)
};
},
getUrl: async (sandbox, options) => {
return `https://${sandbox.id}.example.com:${options.port}`;
}
}
}
});
API 参考
defineProvider(config)
定义具有沙盒生命周期方法的新供应商.
参数:
interface ProviderConfig<TInstance, TConfig> {
name: string;
defaultMode?: 'direct' | 'gateway';
methods: {
instances: InstanceMethods<TInstance, TConfig>;
template?: TemplateMethods<TTemplate, TConfig>;
snapshot?: SnapshotMethods<TSnapshot, TConfig>;
};
}
methods.sandbox 仍为 methods.instances ——使用 instances 在新供应商.
回返情况: 工厂功能 (config: TConfig) => Provider
必要实例方法
收集操作
这些方法管理收集层面的沙盒:
create(config, options?)
创建新沙盒 .
create: async (config, options) => {
// Return: { instance: YourSandboxObject, instanceId: string }
return { instance: myInstance, instanceId: 'sandbox-123'
};
}
getById(config, instanceId)
以身份证获取已有的沙盒 .
getById: async (config, instanceId) => {
// Return: { instance, instanceId } or null if not found
return { instance: myInstance, instanceId };
}
list(config)
列出所有沙盒 .
list: async (config) => {
// Return: Array of { instance, instanceId }
return [
{ instance: sandbox1, instanceId: 'id1' },
{ instance: sandbox2, instanceId: 'id2' }
];
}
destroy(config, instanceId)
毁一沙盒.
destroy: async (config, instanceId) => {
await yourApi.deleteSandbox(instanceId);
}
实例操作
这些方法适用于单个沙盒实例:
runCommand(sandbox, command, args?, options?)
运行 shell 命令 .
runCommand: async (sandbox, command, args) => {
const result = await yourApi.runCommand(sandbox.id, command, args);
return {
stdout: result.stdout,
stderr: result.errors,
exitCode: result.code
};
}
getInfo(sandbox)
获取沙盒信息.
getInfo: async (sandbox) => {
return {
id: sandbox.id,
status: 'running', // 'running' | 'stopped' | 'error'
createdAt: new Date(sandbox.created_at)
};
}
getUrl(sandbox, options)
访问沙盒服务时获取 URL .
getUrl: async (sandbox, options) => {
return `https://${sandbox.id}.example.com:${options.port}`;
}
可选文件系统方法
通过执行 filesystem 方法。 。 。 导入 escapeShellArg 从 @computesdk/provider 并且总是插入 双引号——帮助者逃脱 \, (中文(简体) ), ", (中文(简体) ), $,并反向缩写,但不包括空格或外壳元体,例如 ; 财务报告和审定财务报表 |,因此未引用的用法会在有空格的路径上断开,对于用户控制的输入来说是不安全的.
import { defineProvider, escapeShellArg } from '@computesdk/provider';
// ...
methods: {
instances: {
// ... required methods
filesystem: {
readFile: async (sandbox, path, runCommand) => {
const result = await runCommand(sandbox, `cat "${escapeShellArg(path)}"`);
return result.stdout;
},
writeFile: async (sandbox, path, content, runCommand) => {
await runCommand(sandbox, `cat > "${escapeShellArg(path)}" << 'EOF'\n${content}\nEOF`);
},
mkdir: async (sandbox, path, runCommand) => {
await runCommand(sandbox, `mkdir -p "${escapeShellArg(path)}"`);
},
readdir: async (sandbox, path, runCommand) => {
const result = await runCommand(sandbox, `ls -la "${escapeShellArg(path)}"`);
// Parse ls output and return FileEntry[]
return parseFileList(result.stdout);
},
exists: async (sandbox, path, runCommand) => {
const result = await runCommand(sandbox, `test -e "${escapeShellArg(path)}"`);
return result.exitCode === 0;
},
remove: async (sandbox, path, runCommand) => {
await runCommand(sandbox, `rm -rf "${escapeShellArg(path)}"`);
}
}
}
}
说明: 如果你不执行 filesystem,沙盒仍然会工作,但文件系统操作会抛出有用的"不支持"错误.
背景进程和流程
instance.startProcess(...) 财务报告和审定财务报表 runCommand(cmd, { onStdout, onStderr }) 每个供应商的工作。 框架按此顺序选择运行时间:
- 土著 - 供应商自己
streamCommand编号 :startProcess钩上methods.instances。 。 。 . - 外壳分级( 默认) —— POSIX- shell 进程记录只建立在提供者平原上
runCommand。节点、守护进程或下載等实例,因此它可以工作在任何图像上sh包括高山和繁忙的盒子. - 守护进程( opt- in) ——箱式守护进程,仅在提供者配置时
processRuntime: 'daemond'数字 :
const provider = e2b({ apiKey, processRuntime: 'daemond' });
instance.processRuntime (b) 报告为每一能力服务的级别,例如:. { streaming: 'native', processes: 'shell' }。 。 。 .
贝壳级
每个工作都是目录 ${TMPDIR:-/tmp}/.computesdk/jobs/<jobId>/ (英语:Mode 0700). 一个执行官写工作文件 并派出一名主管 nohup setsid;主管通过 FIFOs 将命令在自己的流程组中运行并进行 stdout/stder 运行到被设定的日志中(out.log,记录 O<len>\n… 编号 : E<len>\n… ,然后写 pid 财务报告和审定财务报表 exit。 。 。 .
- 读取 (单位:千美元)
status, (中文(简体) ),wait, streaming)是每个执行器一个,取一个字节光标并只返回新输出,每执行器最多为16个日志的KiB(约22个KB Base64,在SSM的24000个字符内置封顶下).wait每名执行官最多25分. - 静态 (单位:千美元)
stdin: true- 持单人程序保持的FIFO;write是一个执事者,closeStdin杀死持有者. - 杀死 表示整个进程组。 流线
runCommand是因为timeout由监督员在盒子中执行,监督员是SIGKILLL 组(出局码为124),因此,当客户端消失时,最后期限就到了。 无timeout没有任何东西可以杀死指挥部. - 重装数字 :
instance.attachProcess(jobId)在新客户端中重建一个手柄. - 保留:退出后10分钟(杀后60分),工作目录被移除.
- 执行者共享无状态的实例 (每个命令有一个新鲜的容器,例如Archil磁盘Exec):一个实例上的第一次发射会维持这个命令,直到第二个执行确认工作目录幸存,所以在任何东西运行之前都会发现这个. 在这种情况下,流出
runCommand回到一个简单的执行器,其输出在出口交付startProcess掷出ShellStateNotPersistentError。 。 。 . - 所需资源数字 :
sh, (中文(简体) ),setsid, (中文(简体) ),mkfifo, (中文(简体) ),kill, (中文(简体) ),cat, (中文(简体) ),dd, (中文(简体) ),wc, (中文(简体) ),tail, (中文(简体) ),head, (中文(简体) ),base64, (中文(简体) ),mkdir, (中文(简体) ),rm, (中文(简体) ),mv, (中文(简体) ),sleep。一个缺失的失败ShellProcessUnsupportedError点名.
进取路由器( :CreateInstanceOptions.egress)留在守护进程上:它的shim是一个节点程序,守护进程靴子是将节点安装在图像上而没有它的东西.
类型定义
核心类型
import type {
// Provider types
Provider,
ProviderConfig,
ProviderInstance,
// Method definitions
InstanceMethods,
TemplateMethods,
SnapshotMethods,
// Result types
CodeResult,
CommandResult,
InstanceInfo,
// Options
CreateInstanceOptions,
RunCommandOptions,
// Filesystem
FileEntry,
InstanceFileSystem
} from '@computesdk/provider';
提供者模式
供应商可以以不同方式运作:
direct- 供应商具有本地沙盒能力(例如E2B、Modal)gateway- 供应商只有基础设施 -- -- 通过网关的路线
在提供者配置中设置默认模式 :
export const myProvider = defineProvider({
name: 'my-provider',
defaultMode: 'direct', // or 'gateway'
methods: { /* ... */ }
});
完整示例
这里有一个完整的最小提供者执行:
import { defineProvider } from '@computesdk/provider';
interface MinimalConfig {
apiKey: string;
}
export const minimal = defineProvider<any, MinimalConfig>({
name: 'minimal',
defaultMode: 'direct',
methods: {
instances: {
create: async (config, options) => {
const id = `sandbox-${Date.now()}`;
return {
instance: { id, status: 'running' },
instanceId: id
};
},
getById: async (config, id) => {
return {
instance: { id, status: 'running' },
instanceId: id
};
},
list: async (config) => {
return [];
},
destroy: async (config, id) => {
// Clean up sandbox
},
runCommand: async (sandbox, command, args) => {
return {
stdout: `Ran: ${command} ${args?.join(' ')}`,
stderr: '',
exitCode: 0
};
},
getInfo: async (sandbox) => {
return {
id: sandbox.id,
status: 'running',
createdAt: new Date()
};
},
getUrl: async (sandbox, options) => {
return `http://localhost:${options.port}`;
}
}
}
});
// Export factory function
export function createMinimal(config: MinimalConfig) {
return minimal(config);
}
供应商包中的使用
一旦您定义了您的供应商, 从您的软件包中导出 :
// In @computesdk/my-provider/src/index.ts
import { defineProvider } from '@computesdk/provider';
export const myProvider = defineProvider({ /* ... */ });
// Users can then use it directly
import { myProvider } from '@computesdk/my-provider';
const compute = myProvider({ apiKey: 'xxx' });
const sandbox = await compute.instances.create();
最佳做法
1. 提前验证配置
create: async (config, options) => {
if (!config.apiKey) {
throw new Error(
'Missing API key for my-provider.\n\n' +
'Setup instructions:\n' +
'1. Get your API key from https://example.com/keys\n' +
'2. Set environment variable: export MY_PROVIDER_API_KEY=your_key\n' +
'3. Or pass it directly: myProvider({ apiKey: "your_key" })'
);
}
if (!config.apiKey.startsWith('mp_')) {
throw new Error(
'Invalid API key format for my-provider.\n' +
'API keys should start with "mp_"'
);
}
// Continue with creation
}
2. 提供帮助错误消息
runCommand: async (sandbox, command, args) => {
try {
return await yourApi.runCommand(sandbox.id, command, args);
} catch (error) {
if (error.code === 'QUOTA_EXCEEDED') {
throw new Error(
'API quota exceeded for my-provider.\n' +
'Upgrade your plan at https://example.com/pricing'
);
}
throw error;
}
}
3. 慷慨地处理缺失的地物
如果您的提供者不支持某些特性:
// Don't implement filesystem methods - ComputeSDK will auto-generate
// helpful error messages for users
4. 文件提供者 -- -- 具体特点
/**
* My Provider - Fast cloud sandboxes
*
* Features:
* - Python and Node.js runtimes
* - Full filesystem support
* - GPU acceleration (premium plans)
* - No terminal support
*/
export const myProvider = defineProvider({ /* ... */ });
测试您的提供者
创建测试以验证您的提供者执行 :
import { describe, it, expect } from 'vitest';
import { myProvider } from './index';
describe('MyProvider', () => {
it('creates and destroys sandboxes', async () => {
const compute = myProvider({ apiKey: process.env.MY_PROVIDER_API_KEY! });
const sandbox = await compute.instances.create();
expect(sandbox.instanceId).toBeDefined();
const info = await sandbox.getInfo();
expect(info.status).toBe('running');
await sandbox.destroy();
});
it('executes code', async () => {
const compute = myProvider({ apiKey: process.env.MY_PROVIDER_API_KEY! });
const sandbox = await compute.instances.create();
const result = await sandbox.runCommand('python -c "print(\"Hello\")"');
expect(result.stdout).toContain('Hello');
expect(result.exitCode).toBe(0);
await sandbox.destroy();
});
});
实例
参见这些提供者的实施参考:
- @ computesdk/e2b - 拥有文件系统操作的全能提供者
- @ computesdk/ 模式 - GPU加速Python供应商
- @ computesdk/ daytona (帮助) - 基于工作空间的发展沙盒
相关软件包
- 计算 - 面向用户的网关SDK
- @ computesdk/e2b - E2B供应商的实施
- @ computesdk/ 模式 - 模块提供者的落实
许可证
麻省理工学院
纯标准大小的能力钩
defineProvider 接受可选同步 sizes(config, profile, options)
钩. 遗产习惯 Provider 执行不需要添加此字段。 工厂
供应商暴露出不可改变的 provider.sizes.list(context?) 编号 : resolve(class, context?) 管理器; 没有钩子报告的适配器 unknown 并且否认明白的
sizeClass 请求而不是忽略它们。 背景可以包括模式或
平台选择器,但不得合并标准大小控件和本地大小控件.
sizes: (_config, profile, options) => {
if (options?.snapshotId) {
return { status: 'unsupported', reason: 'Restores inherit resource sizing.' };
}
// Declare supported only after verifying exact CPU + RAM bounds and semantics.
return {
status: 'supported',
nativeOptions: { vcpus: profile.vcpus, memoryMiB: profile.memoryMiB },
};
},
工厂应用平地并合并已知的命名空间
instanceOptions.shape 资源路径
创建选项的副本,条 sizeClass,并记录不可更改的请求
元数据为 instance.sizing. 虎克必须只返回资源绘图场,
永远不要复制呼叫器 env/metadata/ 授权/通过机构进入补丁 .
这种被捆绑的合并保留了无关的嵌入地,没有被冻结的呼叫器
对象; 它不会递归正态任意创建选项。 标准
资源背景和释放的补丁拒绝不安全的密钥/存取器; 普通和
接受无效原型数据对象。 没有阶级的要求是没有改变的.
业务 resolution.nativeOptions 与 instance.sizing,哪个
只包含已请求/已解决的CPU/RAM/类和出处.
它们必须是纯的: 没有客户端构建, 证书检查,模板列表,
网络接入、构建或提供。 支持的映射是适配器声明
* 请求、未计量资源或账户/区域可用性担保.
返回时 unconfigured, (中文(简体) ), unavailable, (中文(简体) ), requires-build, (中文(简体) ), unknown,或 unsupported
当精确的映射无法宣告时, 没有后退或自动
建设发生。 发现结果被复制并被冻结(包括巢原生地)
选项);调用输入不被冻结或变异.
createCompute 保护传统定制供应商 缺乏规模合同.
defineInfraProvider 目前拒绝标准大小请求为 unknown; 其编号
传统本地创建路径不变。 直接调用到手写习惯
提供者仍然是提供者作者的责任.
一个 InstanceSizeManager 携带 contract: 'computesdk.instance-sizes.v1' 将制作
明确和区分不相关的遗产 sizes 属性。 手写
宣布该合同的供应商对实际消费负有责任
sizeClass (b) 框架保证:
defineProvider 适应器. 核心拒绝不兼容/有缺陷的声明.
标准选项、限定的本地资源背景以及释放的补丁使用
假设自己的字符串键数据描述符。 符号/不可假设/接入域
在钩/复制/创建之前拒绝; 带有定义的函数 sizeClass 无法
选择退出验证。 失踪/未定义 sizeClass 保留遗产通过.
关于源相容性, 基础 Provider.sizes 编号 : DirectProvider.sizes 是一个
选项 unknown 扩展槽:遗留数组、函数或无关对象
即使没有标准要求,仍然可以转让。 使用 isInstanceSizeManager
在发现前缩小这个位置. defineProvider sDK适配器工厂
仍然返回需要的, 强烈键入 sizes 管理器; 缺失/不兼容
运行时合同在创建前仍然拒绝定义的标准请求 .
仅由编译器保存的相容性覆盖包含在本包中
tsconfig.json (单位:千美元)src/**/*,包括: src/__tests__/legacy-size-types.ts) (中文(简体) ). 运行
它明确来自没有网络/安装的工作空间根:
pnpm exec tsc --noEmit --strict --target es2020 --moduleResolution node --esModuleInterop --skipLibCheck packages/provider/src/__tests__/legacy-size-types.ts
一般的模拟套房也运行 legacy-size-types.test.ts,引用
已安装此固定线上的 TypeScript 编译器(--noEmit; 计 数
fixture 的只类型实例调用从未执行 。 没有安装/ 网络步骤
需要,所以结构后退失败 pnpm test 我也一样.