提供方接线计划(对现有 CLI 模块的后续修改)
状态(2026-10-13):沙箱路径的接线已经完成;本文现在是原计划的记录,加上仍未解决的事项清单。
provider已是命令组,--provider/--opt/--all-providers和带前缀的 id 可用,Sandbox.create/connect/list/killById会选择提供商,SandboxNotFoundError(E2009)已存在,network.resolve走ports.url,并且已从 CLI 端到端驱动过一个 OpenSandbox 服务器(验证记录)。下面第 1 到 8 节保留为原计划,代码已经做到的条目请忽略。未解决事项见「10. 已知缺口」。此前的状态:交接说明,没有修改任何现有命令模块。本文按评审修复合入之后的应用顺序,列出剩余的精确修改。语法、帮助文本和 JSON 形状由providers-and-credentials-cli.md负责;本文只说明每一部分接在哪里。这是英文版的中文版,章节编号一致。命令名、键名、错误码和代码标识符保持英文原样;两者有差异时以英文版为准。
0. 已有内容(不要重写)
| 领域 | 模块 | 需要调用的入口 |
|---|---|---|
| 键注册表 | src/utils/config_keys.ts(由 scripts/gen-config-keys.mjs 从 tests/fixtures/config-keys.json 生成)、src/utils/config_resolver.ts |
findKeyByAlias、envNames、aliasNames、providerEnvName;resolveKey、resolveProviderId(层优先) |
| 分层配置 | src/transport/config_layers.ts |
loadLayeredConfig、LayeredConfig.toTransportConfig()、explainConfig、auditConfig、planMigration、applyMigration、warnLegacyOnce |
| 提供方核心 | src/providers/(index.ts 汇总导出) |
getProvider、listProviders、resolveProviderId、ambientProviderId、parseSandboxRef、qualify、install、update、uninstall、doctor、defineProviderContractTests、runContractSuite、exitCodeFor |
| 内置提供方 | src/providers/builtin.ts |
AlicloudExpressProvider 原样包装现有的 HttpClient 与各 Protocol |
| 插件 | exec_provider.ts(EPP/1)、rest_provider.ts |
按清单 kind 选择 |
下面都是接线工作。名称沿用 Python 的名称,线上字段为 snake_case。
1. 前置条件
- 版本号:
package.json及src/**中的版本升到0.3.0,与python-sdk同步。参考插件声明compat.requires = { cli = ">=0.3" };当前测试显式传入hostVersion: "0.3.0"。升级后删除tests/_support/plugins.ts中的TEST_HOST_VERSION覆盖。 package.json:files已包含dist,src/providers/**会编译进去,无需修改。清单解析器使用仓库内的src/utils/toml_plain.ts,没有新增依赖。models/errors.ts:新增SandboxNotFoundError(错误码E2009,对应 Pythonmodels/errors.py)。providers/errors.ts#exitCodeFor须将其映射为退出码 4。protocol/sandbox.ts:get、connect、set_timeout、pause、resume遇到 HTTP 404 时抛出SandboxNotFoundError(kill仍返回false),与 Python 一致。models/sandbox.ts:SandboxConfig增加secure?: boolean和provider_options?: Record<string, unknown>(内置适配器目前用Object.assign携带,需换成真正的字段)。ProcessInfo.pid改为number | string(exec 提供方使用字符串process_id)。api/network.ts:增加resolve(port)/resolve_url(port),非 envd 提供方通过ports.url解析(对应 Pythonapi/network.py)。内置行为不变。
2. src/api/sandbox.ts(API 层)
现状:顶部的 _buildInfra(opts) 依次构建 loadConfig → createAuthProvider → HttpClient → SandboxProtocol,Sandbox.create/connect/list/killById 调用它。
修改:
BuildInfraOptions增加provider?: string | null与provider_options?: Record<string, unknown> | null。_buildInfra:const p = getProvider(resolveProviderId(opts.provider));若p.id === BUILTIN_PROVIDER_ID则原样走现有代码路径(保证所有基于 mock/spy 的测试字节级不变);否则await p.buildInfra(loadLayeredConfig(overrides))并映射为同样的元组。仅插件分支是异步的;保留内置的同步快路径,或在 4 个调用点统一await。Sandbox.create(template, { provider, provider_options, secure, ... }):将provider_options与secure传入SandboxConfig;功能门控已在ExecProvider适配器内部完成。Sandbox.connect(ref)/killById(ref):parseSandboxRef(ref)得到[pid, sid];前缀决定提供方,与显式--provider冲突时抛E8030(ProviderMismatchError)。无前缀的 id 使用环境默认提供方。Sandbox.list({ provider })对单个提供方不变。新增静态方法listAllProviders():遍历listProviders(),容忍单个提供方失败(返回{ results, errors }),用qualify(providerId, id)标记每一行。- exec 提供方返回
envd_url时,继续使用现有 envd 传输做 exec / 文件 / PTY(混合模式,已在ExecProvider中实现)。 - 取消
tests/test_providers/test_sandbox_provider.test.ts中两个it.todo的占位(Sandbox.connect('provider:id')冲突 →E8030,以及内置默认路径)。
api/commands.ts:路由不变;只需让 kill/connect/send_stdin 的签名接受字符串 pid(随 ProcessInfo.pid 的放宽而改)。
3. src/cli/main.ts 与 command_surface.ts
- 注册命令组:在
src/cli/command_surface.ts的GROUP_COMMANDS中增加provider: "commands/provider:provider";abx --help把它列在单独的 Providers 标题下。 - 根命令选项(根级与叶子级均可,叶子级优先;规格 §4):
--provider ID(envvar: "ABX_PROVIDER"),按 id 语法校验,存入 click 上下文ctx.obj.provider。--opt KEY=VALUE(可重复),用src/providers/testing.ts中的parseProviderOptions解析(也可挪到providers/options.ts)。涉及密钥字段(ConfigField.secret)时按规格以退出码 2 拒绝。abx --help结尾加入Related commands行(规格 §0),重新生成根帮助 golden。- 顶层错误处理:仅对提供方类型的错误使用
exitCodeFor(err),其余保持现有映射(golden 不变):认证 3、未找到 4(现在包含SandboxNotFoundError)、超时 5、配额 6、配置 / 信任 / 不匹配 2,其余 1。 ABX_WARN_DEPRECATED=1:main.ts无需改动,warnLegacyOnce在调用时读取环境变量。CLI 测试的 setup 中需调用_resetDeprecationWarnings()。
4. 新文件 src/cli/commands/provider.ts
一个 provider 组及子命令,每个都是对库的薄封装(命名见规格 §5;均支持 --json/--quiet/--ci/--no-color):
| 命令 | 调用 | 说明 |
|---|---|---|
list |
listProviders() |
列按规格 §5.1;标出默认项 |
info ID |
getProvider(id) + 清单 / 回执 |
显示功能、字段(密钥遮蔽)、信任级别、摘要 |
install SOURCE [--ref --sha256 --subdir --link --yes --allow-insecure-transport --force] |
install(source, { confirm, verify: verifyHandshake, … }) |
confirm 打印 InstallPlan 同意界面;非 TTY 且无 --yes → 退出码 2 |
uninstall ID [--purge] |
uninstall |
除非 --purge,否则保留 data/;若为默认提供方则重置并提示 |
update [ID \| --all] [--check] [--yes] [--allow-insecure-transport] |
update |
--check 只读;风险项变化(diffAspects)需重新同意 |
default [ID] |
写入 config.toml 的 [core] provider(用 toml_write.ts) |
显示生效值与来源;ABX_PROVIDER 覆盖时给出提示 |
config ID [KEY [VALUE]] |
providerSchema(id) |
存储规则见规格 §3.3 |
doctor [ID] |
doctor(id, { handshake: true }) |
展示 installed/integrity/config/handshake 检查;任一失败退出码 1 |
test ID [--opt …] [-k FILTER] |
runContractSuite(id, { options, filter }) + summarizeContract |
逐用例输出 ok/skip/fail;失败退出码 1;SkipContractTest 记为跳过 |
库抛出的是带 code(E80xx)和 suggestion 的 ProviderError 子类;用现有错误格式化器渲染,并通过 exitCodeFor 退出。
5. src/cli/commands/config_cmd.ts
config_cmd.ts 按每个键唯一的名字解析。用这条规则替换旧的多名字表,顺序如下:
- 键解析(
get/set/delete KEY):层优先的resolveKey查找该键唯一的环境变量。限定形式providers.<id>.<key>和--provider选择提供方。未知键以退出码 2 结束,并列出存在的键(规格 §3.2)。 - 读取:
get/list使用loadLayeredConfig()/explainConfig()(来源标签)。输出通道与格式不变。 - 写入:
config set写入该键唯一的环境变量(密钥在~/.abx/.env,其他键用配置键名写入config.toml)。delete删除该已存值。 get/set/delete/list接受--provider和--all。config list显示sandbox_base_url、sandbox_domain以及注册表里的其他键。explainConfig()报告决定每个键的那一个变量。密钥脱敏。config list显示每个键那一个名字,并脱敏密钥。没有第二个名字需要审计或改写。- 提供方作用域的键(
acr_*、team_id、region、插件字段):config set <provider>:<key>或配合--provider;密钥写入~/.abx/.env(0600)。
需先补的缺口:config-keys.json 没有 description_zh/required;若 config list 需要中文描述,请从 Python 仓库重新生成该 fixture(漂移测试会强制更新)。
6. src/cli/commands/sandbox.ts
修改位置(行号会漂移,按命令名查找):
create:增加--provider、可重复的--opt、--secure/--no-secure;把provider、provider_options、secure传给Sandbox.create。作用域为内置提供方的选项(--region、--access-key-*等)用于其他提供方时,按规格文本以退出码 2 拒绝。输出qualify(providerId, id),且仅当提供方不是内置时才在--json中加入"provider"(否则字节级不变)。list(list_cmd):增加--provider与--all-providers(JSON:仅非内置或全提供方模式下每行加provider键;单个提供方失败以 stderr 警告输出,除非全部失败否则退出码 0)。kill、connect,以及所有接受沙箱 id 的命令(sandbox_files.ts、sandbox_process.ts、sandbox_system.ts):通过parseSandboxRef接受限定的provider:id,调用Sandbox.connect(ref, { provider })。loadConfig({ region })调用点(create/kill):内置提供方保持不变;非内置走loadLayeredConfig。info:仅对非内置提供方增加Provider行。- 根级
--provider/--opt从ctx.obj读取,叶子级覆盖。
sandbox.yaml(provider: 键):在沙箱规格加载器(src/cli/commands/sandbox.ts 的声明式创建路径及 src/api/_yaml.ts 的使用方)中增加可选的 provider: <id> 和 provider_options: {…};优先级:命令行选项 > sandbox.yaml > 环境默认。未知或密钥类选项名与 --opt 一样以退出码 2 拒绝。
7. src/cli/commands/mcp.ts
- 服务端环境(
mcp start构建env处,_spawnBackground附近)仅在进程环境中显式存在时转发ABX_PROVIDER与ABX_<P>_*提供方变量;绝不把 LLM / GitHub 密钥转发给提供方(注册表在构造提供方时已强制,无需额外处理)。 - 认证令牌辅助函数中的
loadConfig().api_key(约 162 行)保持不变:它是 MCP 认证使用的内置密钥查找。 - 创建 / 列出 / 终止沙箱的 MCP 工具:在
sandbox_create、sandbox_list、sandbox_kill、sandbox_connect的工具 schema 中增加可选字符串参数provider,接受限定 id;JSON 结果仅对非内置提供方加入provider。 mcp install复制规则(规格 §4.3):生成的客户端配置只通过ABX_PROVIDER保留提供方选择,绝不包含密钥。
8. 文档与 golden
- 重新生成 golden:
config list(文本 / JSON)、config --help、abx --help;为provider命令新增 golden。 - 更新
docs/en/reference、docs/en/guide/{environment-variables,authentication,troubleshooting}.md,新增docs/{en,zh}/guide/providers.md;中英文镜像同步;SKILL.md、llms.txt、README*.md。 - 文档中不得出现本地绝对路径(
scripts/check-no-local-paths.sh)。
9. 接线后的验证清单
npx tsc -p tsconfig.json --noEmit与npm run lint。npx vitest run tests/test_providers tests/test_transport/test_config_names.test.ts tests/test_utils(当前 providers 为 338 通过、2 todo)。- 仅内置提供方时
parity/diff/下的差分用例:除config list外字节级一致。 - 对
local-process、e2b-compatible以及(本地 OpenSandbox 服务在运行时)opensandbox运行abx provider test <id>;最后一个在服务不可用时礼貌跳过。 - 跨实现:用 Node 安装器安装插件后由 Python SDK 加载(摘要相同),反向亦然。
10. 已知缺口(带入评审)
2026-10-13 OpenSandbox 实测之后仍未解决的事项(每条都有已验证的现象):
template命令忽略--provider。abx --provider opensandbox template deploy ...会走阿里云 ACR 流程(ACR credentials missing)。它们应当检查templates.read/templates.build,像其他受特性约束的命令一样返回E8004。- 能力集合不是从提供商的特性推导出来的。 对 OpenSandbox,
abx sandbox capabilities和sandbox.capabilities报告code, files, shell,而它并未声明code.interpret;Provider.capabilities()与FEATURE_TO_CAPABILITY存在,但没有任何调用方。基于镜像的提供商每次 create 还会打印Could not resolve capabilities for template '<image>'。 - 在没有 HTTP 数据面的插件上执行
abx sandbox command run会以this._http_client._createEnvdClient is not a function失败。Python SDK 有_require_data_plane("named commands"),并返回E8004;应把同样的保护移植到src/api/sandbox.ts的_callServerCommand。 SandboxInfo的默认值(region = "cn-hangzhou"、timeout = 300)属于内置平台。recordToInfo现在对插件覆盖了这两个值,所以既不报告区域也不报告生命周期的插件会显示空的 Region 行和请求时的 timeout;模型应当把二者改为可选。- 没有 pause/resume 的 CLI 命令:
Sandbox.pause()/resume()可用(已在 Python 中验证),但这个树里没有abx sandbox pause。 - 目录仓库仍带着
ebx-provider.toml。 需要在alibaba-agent-sandbox/awesome-providers中应用e2e/providers/awesome-providers-abx-rename.patch;在此之前,从目录执行abx provider install会以E8014(插件根目录没有abx-provider.toml)失败。
更早的缺口:
FilesystemProtocol.watchDir仅支持原生;exec 提供方的files.watch返回E8004,除非走混合模式。- 提供方错误类位于
providers/errors.ts;若框架要求单一错误模块,请迁移或重新导出。 - 旧的
transport/config.ts通过LayeredConfig.toTransportConfig()桥接;待config_cmd.ts与mcp.ts不再导入loadConfig后即可移除。 - Node 中没有基于入口点的提供方发现(只有已安装的插件和
registerProvider())。