Skip to main content
内置的三个 sandbox agent(claude-codecodexbub)做到的能力不完全相同——能力没有声明这一层,全由构造方式和实际行为证明,见能力位参考。这篇按能力盘点三个内置 agent 各自做到哪,以及两个接 AI SDK 应用的内置件:无侵入 HTTP adapter uiMessageStreamAgent(含 HITL)和结果转换器 turnFromAiSdk

能力速览

三个内置 agent 分别做到哪

三者都用 defineSandboxAgent 构造(Agent.kind 恒为 "sandbox"),所以 t.sandbox.fileChanged() / diff 断言、文件 IO、命令执行在三个 agent 上都能用,不受这张表限制。
三个内置 sandbox agent 都不支持 HITLsend 只返回 "completed" / "failed",从不返回 "waiting",也不吐 input.requested 事件。需要 t.respond() / t.requireInputRequest() 时:被测对象是 AI SDK useChat 后端的话,用下文的内置 uiMessageStreamAgent(v7 tool approval 原生映射成 HITL)。其它被测对象自己写 adapter:实现 waiting 状态 + input.requested 事件 + resume 交回,examples/zh/tier1/pi-sdk / examples/zh/tier1/claude-sdk 都有手写 HITL 的现成参考。

逐 agent 细节

claude-code

  • 连接方式:Sandbox 里 spawn claude --print --dangerously-skip-permissions,读回 ~/.claude/projects/**/*.jsonl 最新一份 transcript。
  • 会话续接:ctx.session.id 有值时追加 --resume <id>;transcript 解出的会话 id 经 ctx.session.capture() 写回。
  • 鉴权:ANTHROPIC_API_KEY,可选 ANTHROPIC_BASE_URL;配置项见下方 ClaudeCodeConfig
  • tracing 通过 claude CLI 原生 OTLP trace spans(beta)配置,协议为 http/protobuf。在 env 中设置 CLAUDE_CODE_ENABLE_TELEMETRYCLAUDE_CODE_ENHANCED_TELEMETRY_BETA(须显式开启的 beta 开关),即可把 endpoint 交给 CLI。trace decoding 显示 interaction / llm_request / tool 层级的瀑布图。

codex

  • 连接方式:Sandbox 里跑 codex exec --json(续接时是 codex exec resume <id> --json),stdout JSONL 当 transcript。命令带 --dangerously-bypass-approvals-and-sandbox--dangerously-bypass-hook-trust:Sandbox 里没人能回答 codex 的交互确认,插件或 postSetup 装好的 hook 因此不需要交互授信就能生效。
  • 鉴权:CODEX_API_KEY(不是 OPENAI_API_KEY),可选 CODEX_BASE_URL 接 OpenAI 兼容代理。配置项见下方 CodexConfig
  • tracing 通过 ~/.codex/config.toml[otel.trace_exporter.otlp-http] 段配置,协议 http/json

bub

  • 连接方式:Sandbox 里跑 bub run + --session-id,从 ~/.bub/tapes/<hash>.jsonl 读 tape 当 transcript。
  • 鉴权:BUB_API_KEY + BUB_API_BASE(OpenAI 兼容代理)。配置项见下方 BubConfig
  • tracing 通过环境变量注入(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT 等),协议 http/protobuf
  • 安装走 uv tool install(非 npm 包),首次安装带 checkpoint 缓存以加速后续 Sandbox。

三个内置 sandbox agent 的配置项

ClaudeCodeConfig

apiKey

Anthropic API key。省略时读 ANTHROPIC_API_KEY env。

baseUrl

自定义 API base URL(代理 / 内网端点)。省略时读 ANTHROPIC_BASE_URL env; 两者都没有则用 Anthropic 官方端点(claude CLI 默认行为)。

env

Extra process environment for Claude Code. Values stay private runtime data and do not enter carry identity. Mirror behavior-changing non-secret values into Experiment flags or the owning Plugin identity.

maxTurns

最多跑几个 tool-use 轮次(→ --max-turns)。 控制评估用例成本上限;省略时用 CLI 原生默认(无限制)。

mcpServers

额外 MCP server(每个 Sandbox setup 时写进用户级 ~/.claude.json)。 stdio 形态写 command(可带 args / env);Streamable HTTP 形态写 url(可带 headers, 逐字进请求头),落成 { “type”: “http”, “url”: …, “headers”: … } 条目。

skills

装进 Sandbox 的 Skill(本地目录/文件,或 repo + 可钉 ref + 可选启用集)。 落在 project 级 .claude/skills/<name>/,claude CLI 原生发现。

plugins

Claude Code 原生 Plugin(先连 Marketplace,再从中装指定 Plugin)。

settingsFile

一份完整的 Claude Code settings.json(官方格式)在本地项目里的路径 —— 相对运行 niceeval 的项目根(含 niceeval.config.ts 的目录)解析,不是 Sandbox 内路径;只接受 项目根内的相对路径,包含 .. 的路径、绝对路径、~ 路径和解析后逃出项目根的符号链接 都在 setup 阶段报错。原始字节原样上传为 Sandbox 里原本为空的用户级 ~/.claude/settings.json (不继承宿主机配置、不拼接、不重新序列化);保留键 modelenv 出现在文件里 setup 报错。manifest 只记项目相对路径与字节 SHA-256,不落正文。

postSetup

安装后按数组顺序运行的用户 Hook(复用 SandboxCommand 的窄上下文):在写 settings、挂 MCP、 装 Skills / Plugin、写 manifest 全部完成后执行,适合跑插件自带的 setup 脚本这类 「安装产物就位后才能跑」的过程动作。抛错按基础设施错误计(attempt errored)。 见 docs/feature/adapters/library/coding-agent-extensions.md「安装后运行脚本」。

preTeardown

postSetup 成对的收尾 Hook:按 postSetup 的逆序语义,在 agent 自己的 teardown 步骤 之前执行(LIFO 镜像 —— postSetup 跑在 agent 安装之后,preTeardown 就跑在 agent 收尾 之前),当且仅当 postSetup 的时点走到过才触发。抛错按基础设施错误计,由 teardown 段 按 teardown-failed 诊断收束。 见 docs/feature/adapters/library/coding-agent-extensions.md「安装后运行脚本」。

CodexConfig

apiKey

代理 / OpenAI API key。省略时读 CODEX_API_KEY env。

baseUrl

OpenAI 兼容代理 base URL(如 https://s2a.example.com/v1)。省略时读 CODEX_BASE_URL env。

env

注入每次 Codex CLI 进程的额外环境变量。首轮 codex exec 与续轮 codex exec resume 使用同一份声明;Codex 启动的 lifecycle Hook、MCP 动态 header 与命令子进程都会继承。 值只经 Sandbox command options 传入,不拼进 shell 文本或写入 setup manifest,并全部按 潜在敏感值从 timing / execution / error 证据中脱敏。CODEX_API_KEY 仍由 apiKey 或 宿主同名环境变量提供,Adapter 的鉴权值会覆盖这里的同名键。 env value 不进入 carry 身份。会改变被测行为的非敏感值必须同时声明在 Experiment flags 或所属 Plugin identity;只轮换凭据不会让旧结果失效。 PATH 是 Sandbox 受管变量,不接受经这里声明——出现即在 factory 构造时报错,改用 Sandbox factory 的 pathPrepend(见 docs/feature/sandbox/library.md)。

mcpServers

额外 MCP server(每个 Sandbox setup 时追加进 ~/.codex/config.toml)。 stdio 形态(command/args/env)写 [mcp_servers.<name>] 的 command 行; Streamable HTTP 形态(url/headers)写 url 行,headers 进 [mcp_servers.<name>.http_headers] 子表。

skills

装进 Sandbox 的 Skill(本地目录/文件,或 repo + 可钉 ref + 可选启用集)。 落在 .agents/skills/<name>/,并写一段发现指引进 AGENTS.md —— codex 没有 Claude Code 那种 原生 Skill 工具,只把文件装进去它不会自己去读(见 memory/codex-no-native-skill-tool.md)。

plugins

Codex 原生 Plugin(先连 Marketplace,再从中装指定 Plugin)。

configFile

一份完整的 Codex config.toml(官方 TOML 格式)在本地项目里的路径 —— 相对运行 niceeval 的项目根(含 niceeval.config.ts 的目录)解析,不是 Sandbox 内路径;只接受 项目根内的相对路径,包含 .. 的路径、绝对路径、~ 路径和解析后逃出项目根的符号链接 都在 setup 阶段报错。原始字节原样并入 Sandbox 里原本为空的用户级 ~/.codex/config.toml (不继承宿主机配置、不解析后重写);保留键 modelmodel_providermodel_providersmodel_reasoning_effortmcp_serversotel 出现在文件里 setup 报错。manifest 只记 项目相对路径与字节 SHA-256,不落正文。

postSetup

安装后按数组顺序运行的用户 Hook(复用 SandboxCommand 的窄上下文):在写主配置、挂 MCP、 装 Skills / Plugin、写 manifest 全部完成后执行,适合跑插件自带的 setup 脚本这类 「安装产物就位后才能跑」的过程动作。抛错按基础设施错误计(attempt errored)。 见 docs/feature/adapters/library/coding-agent-extensions.md「安装后运行脚本」。

preTeardown

postSetup 成对的收尾 Hook:按 postSetup 的逆序语义,在 agent 自己的 teardown 步骤 之前执行(LIFO 镜像 —— postSetup 跑在 agent 安装之后,preTeardown 就跑在 agent 收尾 之前),当且仅当 postSetup 的时点走到过才触发。抛错按基础设施错误计,由 teardown 段 按 teardown-failed 诊断收束。 见 docs/feature/adapters/library/coding-agent-extensions.md「安装后运行脚本」。

BubConfig

apiKey

OpenAI 兼容代理的 API key。省略时读 BUB_API_KEY env。

apiBase

OpenAI 兼容代理的 base URL。省略时读 BUB_API_BASE env。

skills

装进 Sandbox 的 Skill(本地目录/文件,或 repo + 可钉 ref + 可选启用集)。 落在 .agents/skills/<name>/,并写一段发现指引进 AGENTS.md(bub 没有原生 Skill 加载机制)。

version

装哪一版 Bub(PyPI 版本号,如 "0.4.0")。省略时用 NiceEval 钉的默认版本; 永远是确定版本,不装 latest —— 被测对象的版本要能从实验配置读出来。

otelPlugin

OTel tape store 插件的 git 依赖(时间轨的来源)。省略时用 NiceEval 钉的默认 pin。 插件与 Bub 的 tape 协议同代:默认 pin 从 bub.tape 取类型,要求 Bub ≥ 0.3.10;更早的 插件 commit 按 republic 的类型校验,配 Bub ≤ 0.3.9。配错代不会安装失败,而是 span 全被拒、 时间轨静默为空 —— 所以往回钉 version 时必须同批钉配套的插件 commit。

pythonPlugins

额外装进 bub tool 环境的 Python Package,每个 Sandbox setup 时进 uv tool install … --with <pkg>。 规范化后的 package 列表进安装 checkpoint key:plugin 集合不同的两个 agent 变体不会复用同一个 安装 checkpoint(否则第二个变体会静默拿到第一个变体的环境)。

postSetup

安装后按数组顺序运行的用户 Hook(复用 SandboxCommand 的窄上下文):在装 bub、装 Skills / Python package、写 manifest 全部完成后执行。抛错按基础设施错误计(attempt errored)。 见 docs/feature/adapters/library/coding-agent-extensions.md「安装后运行脚本」。

preTeardown

postSetup 成对的收尾 Hook:按 postSetup 的逆序语义,在 agent 自己的 teardown 步骤 之前执行(LIFO 镜像 —— postSetup 跑在 agent 安装之后,preTeardown 就跑在 agent 收尾 之前),当且仅当 postSetup 的时点走到过才触发。抛错按基础设施错误计,由 teardown 段 按 teardown-failed 诊断收束。 见 docs/feature/adapters/library/coding-agent-extensions.md「安装后运行脚本」。

uiMessageStreamAgent:接 AI SDK 应用的内建无侵入 adapter(含 HITL)

uiMessageStreamAgentniceeval/adapter 导出)对着 UI Message Stream 协议(AI SDK useChat 后端的标准 SSE)的 HTTP 端点无侵入收发——只 fetch,不 import 应用代码,应用在哪部署都能接:
它替你做好的能力:
  • 收发 + 事件流:SSE 帧经 ai 包官方的框架无关 reducer readUIMessageStreamuseChat 内部同款)归约,工具调用/结果/消息文本从消息 parts 直构——不要求应用接 OTel
  • 会话续接:协议是服务端零状态、「客户端带全量历史」——工厂用 Adapter 私有的 typed slot 存整份 UIMessage[],每轮原样重放。新会话线(t.newSession() 之后)还没有写过这个 slot。
  • HITL:AI SDK v7 tool approval(工具带 needsApproval: true)原生映射——part 停在 approval-requested 时该 Turn 的 status: "waiting" + input.requestedt.respond("approve" / "deny") 翻译成 approval-responded 原地改写该 part、原样重发 messages 触发服务端续跑(和真实前端 addToolApprovalResponse() + sendMessage() 的协议行为一致)。它没有单独的 approve 端点。
  • 拒绝:调用以 rejected 落进事件流,且默认带「不要重试」的 reason(denyReason 可覆盖)——不带的话模型经常原样重发同一个调用。
  • usage / 瀑布图:UI Message Stream 协议帧里没有 usage,所以 t.maxTokens 这类用量断言在这个内置件上默认没有数据(应用把 usage 放进 message metadata 属于应用自己的协议扩展)。瀑布图是另一回事:应用有 OTel 埋点(如官方 @ai-sdk/otel)时,按 OTel 接入把 span 发给 NiceEvalniceeval view 就有完整瀑布图——span 只进瀑布图,不喂断言。
需要在评估用例项目里安装 ai(可选 peer 依赖,协议 reducer 来自它)。完整可跑示例:examples/zh/tier1/ai-sdk-v7 uiMessageStreamAgent(options) 完整参数(UiMessageStreamAgentOptions):

name

agent 名(报告 / 结果聚合的身份)。默认 “ui-message-stream”。

url

被测应用的 chat 端点(完整 URL,应用在哪部署就指哪);函数形式每轮解析。

headers

附加请求头(鉴权等);ctx.telemetry.headers(traceparent)总会自动并入。

body

messages 外并入请求体的字段,如 (ctx) => ({ model: ctx.model })(undefined 字段序列化时自动丢弃)。

projectToolCommand

由 endpoint owner 逐笔把逻辑工具调用分类为 command 或 not-command。 未知调用返回 undefined 并保持 actions coverage partial;NiceEval 不按名称或 input 猜测。

denyReason

拒绝审批时随 approval-responded 带出的理由。应用/SDK 会把它作为模型看到的工具结果 文本 —— 写清楚「不要重试」能明显降低模型原样重发同一调用的概率(实测)。

settleMs

流结束后再等这么久才返回(毫秒),给应用侧的观测导出(如 BatchSpanProcessor)留时间。

tracing

应用有 OTel 时的端点投递方式(拿瀑布图);事件流不依赖它。

spanMapper

应用有 OTel 时的 span 归一函数(拿瀑布图);事件流不依赖它。

OpenAI 兼容响应转换器:turnFromChatCompletion / turnFromResponses

turnFromChatCompletion(res) / turnFromResponses(res)niceeval/adapter 导出)把 OpenAI 的两种响应形状:Chat Completions 与 Responses。它会把整段响应零映射转成 Turn:不限于 OpenAI 官方,任何声明兼容这两种协议形状的服务都能用。
两种形状对负断言(notCalledTool 等)的可信度不同:Chat Completions 不承诺「响应 = 完整过程」(应用可能在服务端跑完工具循环,只把最终答案给你),负断言只能当「没看到」,不能当「确实没发生」。Responses 的协议契约里 output 数组记录了模型这一轮决定做的全部事(包括每个 function_call),负断言可信。两个转换器产出的 Turn 形状本身相同,这条差异只体现在你对负断言的解读上。

SDK 事件流转换器:createClaudeSdkEventStream / createPiAgentEventStream / createCodexThreadEventStream

各 Agent SDK 的流式协议由 SDK 定义,不是某个应用的私有格式。niceeval/adapter 已经提供原生帧到标准事件的转换器。自己编写非侵入式 Adapter 时,只需填写应用接口和审批接口的地址。driveFrameStream(见下一节)会逐帧读取结果,不需要再写 for 循环:
  • createClaudeSdkEventStreamSDKMessage):assistant 的 text/tool_use 块、user 的 tool_result 块、system/permission_denied(→ rejected)、result 的 usage。markRejected() 登记被拒调用。
  • createPiAgentEventStream(pi-agent-core AgentEvent):message_end 的文本/thinking/usage、tool_execution_start/end 的工具对。
  • createCodexThreadEventStream(Codex SDK ThreadEvent):agent_message/reasoning 的消息类。工具项包括:command_execution / mcp_tool_call / file_change / web_search → 配对的 tool operation.started + operation.finishedturn.completed 的 usage、错误帧。瀑布图要 codex CLI 原生 OTLP(config.toml 的 [otel] 块)把 span 发给 NiceEval,配官方 mapCodexSpans 归一。
可运行的参考实现位于 examples/zh/tier1。其中 Claude SDK、pi SDK 和 Codex SDK 的 Adapter 都由转换器、driveFrameStream 和应用接口配置组成。

通用「拼装方式」件:driveFrameStream / deltaStream + ctx.session

一个手写 send 里真正互不相干的只有三段:transport(怎么发)、reduce(原始数据 → 事件,上一节的转换器管这个)、编排(会话续接 + HITL 暂停恢复)。第三段和任何具体协议无关,纯粹是控制流模式。服务端历史用 ctx.session.id / capture,客户端历史和 HITL 停轮现场用 createSessionSlot<T>() 声明的 Adapter 私有 slot,经 ctx.session.get / set / take 存取(AgentSession,见 Adapter 概念)。剩下真正跨协议复用的只有两个官方件:
三个 Tier 1 示例(claude-sdk、pi-sdk 用 driveFrameStream + typed slot,codex-sdk 只用 driveFrameStream)已经用这套件重写。Adapter 只剩 transport 与 onFrame 里”这一帧要不要额外处理”的判断,没有手写循环和模块级 Map。

turnFromAiSdk:AI SDK 结果 → 事件流转换器

turnFromAiSdkniceeval/adapter 导出)在自己写的 adapter 里用(比如 HTTP web agent 的服务端直构,见 examples/zh/ai-sdk/)。它把 AI SDK 的 generateText / streamText 结果映射成 { events, usage, status }toolCallId 精确配对、时序保真、tool-error 映射成失败的 tool operation.finished、usage 聚合(v4 / v5 / v7 字段漂移都兜住)——直接铺进 Turn
status 由转换器给:有待人批准的 tool approval 时是 "waiting"(并附 input.requested 事件),否则 "completed"。会话续接(多轮 resume)、HITL 的裁决交回、tracing 取决于你自己的 send 怎么写。

怎么选

  • 被测对象是 AI SDK 应用(useChat 后端):内置 uiMessageStreamAgent 无侵入接入,零映射(含 HITL)。
  • 被测对象是其它 agent 系统(HTTP / gRPC 服务):无侵入接入,手写事件映射(官方转换器能覆盖大半)——examples/zh/tier1 下五个示例全是这条路。应用已埋 OTel 的话顺手接上,多一张瀑布图。
  • 需要调用瀑布图(niceeval view 的 trace):claude-code / codex / bub 三个都自发 OTLP(claude-code 走 CLI 的 beta 遥测,span 只有结构与计时)。自己写的 adapter 声明 tracing 即可(见 OTel 指南)。
  • 需要人工审批 / 多步确认(HITL):uiMessageStreamAgent 原生支持(AI SDK v7 tool approval)。Sandbox 型三个都不支持,其它被测对象自己写 Adapter,拿 driveFrameStream + typed slot 拼(Tier 1 示例里 pi-sdk / claude-sdk 有现成写法)。
  • 想跑一个 coding agent 改代码、看 diff、判断工具调用:claude-code / codex / bub(收发 + 事件流 + 会话续接 + workspace + sandbox)。
  • 后端协议是自己发明的、没有官方 SDK 转换器可用时,别从零手写 send。挑一种 reduce 形状(整段落地写小映射,逐 token 增量用 deltaStream),服务端会话续接用 ctx.session.id / capture,客户端历史或 HITL 现场用 typed slot,拼起来就是 send。

相关阅读

  • 接入你的 agent — 自己写 adapter 时每个能力怎么做、对应什么断言。
  • Sandbox Agent — 怎么运行内置 sandbox agent,以及怎么写自己的。
  • Adapter 参考defineAgent / defineSandboxAgent 完整参数。
  • OTel 接入 — 把应用的 span 发给 NiceEval,换 niceeval view 的调用瀑布图。