claude-code、codex、bub)做到的能力不完全相同——能力没有声明这一层,全由构造方式和实际行为证明,见能力位参考。这篇按能力盘点三个内置 agent 各自做到哪,以及两个接 AI SDK 应用的内置件:无侵入 HTTP adapter uiMessageStreamAgent(含 HITL)和结果转换器 turnFromAiSdk。
能力速览
三个内置 agent 分别做到哪
三者都用
defineSandboxAgent 构造(Agent.kind 恒为 "sandbox"),所以 t.sandbox.fileChanged() / diff 断言、文件 IO、命令执行在三个 agent 上都能用,不受这张表限制。
逐 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_TELEMETRY与CLAUDE_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
baseUrl
env
maxTurns
--max-turns)。
控制评估用例成本上限;省略时用 CLI 原生默认(无限制)。
mcpServers
skills
.claude/skills/<name>/,claude CLI 原生发现。
plugins
settingsFile
settings.json(官方格式)在本地项目里的路径 —— 相对运行
niceeval 的项目根(含 niceeval.config.ts 的目录)解析,不是 Sandbox 内路径;只接受
项目根内的相对路径,包含 .. 的路径、绝对路径、~ 路径和解析后逃出项目根的符号链接
都在 setup 阶段报错。原始字节原样上传为 Sandbox 里原本为空的用户级 ~/.claude/settings.json
(不继承宿主机配置、不拼接、不重新序列化);保留键 model 与 env 出现在文件里
setup 报错。manifest 只记项目相对路径与字节 SHA-256,不落正文。
postSetup
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
baseUrl
https://s2a.example.com/v1)。省略时读 CODEX_BASE_URL env。
env
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
skills
.agents/skills/<name>/,并写一段发现指引进 AGENTS.md —— codex 没有 Claude Code 那种
原生 Skill 工具,只把文件装进去它不会自己去读(见 memory/codex-no-native-skill-tool.md)。
plugins
configFile
config.toml(官方 TOML 格式)在本地项目里的路径 —— 相对运行
niceeval 的项目根(含 niceeval.config.ts 的目录)解析,不是 Sandbox 内路径;只接受
项目根内的相对路径,包含 .. 的路径、绝对路径、~ 路径和解析后逃出项目根的符号链接
都在 setup 阶段报错。原始字节原样并入 Sandbox 里原本为空的用户级 ~/.codex/config.toml
(不继承宿主机配置、不解析后重写);保留键 model、model_provider、model_providers、
model_reasoning_effort、mcp_servers、otel 出现在文件里 setup 报错。manifest 只记
项目相对路径与字节 SHA-256,不落正文。
postSetup
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
apiBase
skills
.agents/skills/<name>/,并写一段发现指引进 AGENTS.md(bub 没有原生 Skill 加载机制)。
version
"0.4.0")。省略时用 NiceEval 钉的默认版本;
永远是确定版本,不装 latest —— 被测对象的版本要能从实验配置读出来。
otelPlugin
bub.tape 取类型,要求 Bub ≥ 0.3.10;更早的
插件 commit 按 republic 的类型校验,配 Bub ≤ 0.3.9。配错代不会安装失败,而是 span 全被拒、
时间轨静默为空 —— 所以往回钉 version 时必须同批钉配套的插件 commit。
pythonPlugins
uv tool install … --with <pkg>。
规范化后的 package 列表进安装 checkpoint key:plugin 集合不同的两个 agent 变体不会复用同一个
安装 checkpoint(否则第二个变体会静默拿到第一个变体的环境)。
postSetup
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)
uiMessageStreamAgent(niceeval/adapter 导出)对着 UI Message Stream 协议(AI SDK useChat 后端的标准 SSE)的 HTTP 端点无侵入收发——只 fetch,不 import 应用代码,应用在哪部署都能接:
- 收发 + 事件流:SSE 帧经
ai包官方的框架无关 reducerreadUIMessageStream(useChat内部同款)归约,工具调用/结果/消息文本从消息 parts 直构——不要求应用接 OTel。 - 会话续接:协议是服务端零状态、「客户端带全量历史」——工厂用 Adapter 私有的 typed slot 存整份
UIMessage[],每轮原样重放。新会话线(t.newSession()之后)还没有写过这个 slot。 - HITL:AI SDK v7 tool approval(工具带
needsApproval: true)原生映射——part 停在approval-requested时该 Turn 的status: "waiting"+input.requested。t.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 发给 NiceEval,niceeval view就有完整瀑布图——span 只进瀑布图,不喂断言。
ai(可选 peer 依赖,协议 reducer 来自它)。完整可跑示例:examples/zh/tier1/ai-sdk-v7。
uiMessageStreamAgent(options) 完整参数(UiMessageStreamAgentOptions):
name
url
headers
ctx.telemetry.headers(traceparent)总会自动并入。
body
messages 外并入请求体的字段,如 (ctx) => ({ model: ctx.model })(undefined 字段序列化时自动丢弃)。
projectToolCommand
denyReason
approval-responded 带出的理由。应用/SDK 会把它作为模型看到的工具结果
文本 —— 写清楚「不要重试」能明显降低模型原样重发同一调用的概率(实测)。
settleMs
tracing
spanMapper
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 循环:
createClaudeSdkEventStream(SDKMessage):assistant的 text/tool_use 块、user的 tool_result 块、system/permission_denied(→rejected)、result的 usage。markRejected()登记被拒调用。createPiAgentEventStream(pi-agent-coreAgentEvent):message_end的文本/thinking/usage、tool_execution_start/end的工具对。createCodexThreadEventStream(Codex SDKThreadEvent):agent_message/reasoning的消息类。工具项包括:command_execution/mcp_tool_call/file_change/web_search→ 配对的 tooloperation.started+operation.finished。turn.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 概念)。剩下真正跨协议复用的只有两个官方件:
driveFrameStream + typed slot,codex-sdk 只用 driveFrameStream)已经用这套件重写。Adapter 只剩 transport 与 onFrame 里”这一帧要不要额外处理”的判断,没有手写循环和模块级 Map。
turnFromAiSdk:AI SDK 结果 → 事件流转换器
turnFromAiSdk(niceeval/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的调用瀑布图。