niceeval 0.4.4 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +21 -12
  2. package/README.zh.md +8 -9
  3. package/docs-site/.mintignore +13 -0
  4. package/docs-site/AGENTS.md +46 -0
  5. package/docs-site/concepts/adapter.mdx +287 -0
  6. package/docs-site/concepts/assert.mdx +243 -0
  7. package/docs-site/concepts/drive.mdx +118 -0
  8. package/docs-site/concepts/evals.mdx +108 -0
  9. package/docs-site/concepts/experiment.mdx +37 -0
  10. package/docs-site/concepts/hitl.mdx +83 -0
  11. package/docs-site/concepts/judge.mdx +129 -0
  12. package/docs-site/concepts/overview.mdx +98 -0
  13. package/docs-site/concepts/tier.mdx +42 -0
  14. package/docs-site/docs-ref/00-index.md +23 -0
  15. package/docs-site/docs-ref/01-page-types.md +43 -0
  16. package/docs-site/docs-ref/03-style-rules.md +45 -0
  17. package/docs-site/docs-ref/04-code-examples.md +37 -0
  18. package/docs-site/docs-ref/05-dx-failure-paths.md +45 -0
  19. package/docs-site/docs-ref/06-checklists.md +53 -0
  20. package/docs-site/docs.json +256 -0
  21. package/docs-site/example/ai-agent-application.mdx +148 -0
  22. package/docs-site/example/claude-code-codex-plugin.mdx +162 -0
  23. package/docs-site/example/claude-code-codex-skill.mdx +147 -0
  24. package/docs-site/example/showcase.mdx +39 -0
  25. package/docs-site/example/tier1-ai-sdk-v7.mdx +136 -0
  26. package/docs-site/example/tier1-claude-sdk.mdx +119 -0
  27. package/docs-site/example/tier1-codex-sdk.mdx +111 -0
  28. package/docs-site/example/tier1-langgraph.mdx +119 -0
  29. package/docs-site/example/tier1-pi-sdk.mdx +119 -0
  30. package/docs-site/favicon.svg +5 -0
  31. package/docs-site/github-diff.css +135 -0
  32. package/docs-site/github-diff.js +11 -0
  33. package/docs-site/guides/authoring.mdx +129 -0
  34. package/docs-site/guides/ci-integration.mdx +97 -0
  35. package/docs-site/guides/connect-otel.mdx +209 -0
  36. package/docs-site/guides/connect-your-agent.mdx +221 -0
  37. package/docs-site/guides/dataset-fanout.mdx +98 -0
  38. package/docs-site/guides/experiments.mdx +71 -0
  39. package/docs-site/guides/fixtures.mdx +147 -0
  40. package/docs-site/guides/reporters.mdx +113 -0
  41. package/docs-site/guides/runner.mdx +79 -0
  42. package/docs-site/guides/sandbox-agent.mdx +138 -0
  43. package/docs-site/guides/sandbox-backends.mdx +64 -0
  44. package/docs-site/guides/scoring-guide.mdx +92 -0
  45. package/docs-site/guides/viewing-results.mdx +195 -0
  46. package/docs-site/guides/write-experiment.mdx +81 -0
  47. package/docs-site/guides/write-send.mdx +347 -0
  48. package/docs-site/images/adapter-tiers-zh.svg +60 -0
  49. package/docs-site/images/agent-turn-roundtrip-en.svg +62 -0
  50. package/docs-site/images/agent-turn-roundtrip-zh.svg +77 -0
  51. package/docs-site/images/hitl-handshake-zh.svg +89 -0
  52. package/docs-site/images/logo.svg +6 -0
  53. package/docs-site/index.mdx +181 -0
  54. package/docs-site/introduction.mdx +141 -0
  55. package/docs-site/quickstart.mdx +136 -0
  56. package/docs-site/reference/builtin-agents.mdx +161 -0
  57. package/docs-site/reference/capabilities.mdx +76 -0
  58. package/docs-site/reference/cli.mdx +120 -0
  59. package/docs-site/reference/define-agent.mdx +168 -0
  60. package/docs-site/reference/define-config.mdx +41 -0
  61. package/docs-site/reference/define-eval.mdx +160 -0
  62. package/docs-site/reference/events.mdx +131 -0
  63. package/docs-site/reference/expect.mdx +112 -0
  64. package/docs-site/tracker.js +8 -0
  65. package/docs-site/zh/concepts/adapter.mdx +286 -0
  66. package/docs-site/zh/concepts/assert.mdx +243 -0
  67. package/docs-site/zh/concepts/drive.mdx +118 -0
  68. package/docs-site/zh/concepts/evals.mdx +124 -0
  69. package/docs-site/zh/concepts/experiment.mdx +37 -0
  70. package/docs-site/zh/concepts/hitl.mdx +83 -0
  71. package/docs-site/zh/concepts/judge.mdx +130 -0
  72. package/docs-site/zh/concepts/overview.mdx +109 -0
  73. package/docs-site/zh/concepts/tier.mdx +44 -0
  74. package/docs-site/zh/example/ai-agent-application.mdx +152 -0
  75. package/docs-site/zh/example/claude-code-codex-plugin.mdx +163 -0
  76. package/docs-site/zh/example/claude-code-codex-skill.mdx +147 -0
  77. package/docs-site/zh/example/showcase.mdx +39 -0
  78. package/docs-site/zh/example/tier1-ai-sdk-v7.mdx +140 -0
  79. package/docs-site/zh/example/tier1-claude-sdk.mdx +122 -0
  80. package/docs-site/zh/example/tier1-codex-sdk.mdx +117 -0
  81. package/docs-site/zh/example/tier1-langgraph.mdx +123 -0
  82. package/docs-site/zh/example/tier1-pi-sdk.mdx +122 -0
  83. package/docs-site/zh/guides/agent-feedback-loop.mdx +79 -0
  84. package/docs-site/zh/guides/authoring.mdx +129 -0
  85. package/docs-site/zh/guides/ci-integration.mdx +97 -0
  86. package/docs-site/zh/guides/connect-otel.mdx +208 -0
  87. package/docs-site/zh/guides/connect-your-agent.mdx +226 -0
  88. package/docs-site/zh/guides/dataset-fanout.mdx +83 -0
  89. package/docs-site/zh/guides/experiments.mdx +72 -0
  90. package/docs-site/zh/guides/fixtures.mdx +147 -0
  91. package/docs-site/zh/guides/official-adapters.mdx +132 -0
  92. package/docs-site/zh/guides/reporters.mdx +113 -0
  93. package/docs-site/zh/guides/runner.mdx +82 -0
  94. package/docs-site/zh/guides/sandbox-agent.mdx +139 -0
  95. package/docs-site/zh/guides/sandbox-backends.mdx +75 -0
  96. package/docs-site/zh/guides/scoring-guide.mdx +92 -0
  97. package/docs-site/zh/guides/viewing-results.mdx +195 -0
  98. package/docs-site/zh/guides/write-experiment.mdx +101 -0
  99. package/docs-site/zh/guides/write-send.mdx +353 -0
  100. package/docs-site/zh/index.mdx +180 -0
  101. package/docs-site/zh/introduction.mdx +140 -0
  102. package/docs-site/zh/quickstart.mdx +137 -0
  103. package/docs-site/zh/reference/builtin-agents.mdx +364 -0
  104. package/docs-site/zh/reference/capabilities.mdx +76 -0
  105. package/docs-site/zh/reference/cli.mdx +140 -0
  106. package/docs-site/zh/reference/define-agent.mdx +461 -0
  107. package/docs-site/zh/reference/define-config.mdx +107 -0
  108. package/docs-site/zh/reference/define-eval.mdx +674 -0
  109. package/docs-site/zh/reference/events.mdx +252 -0
  110. package/docs-site/zh/reference/expect.mdx +208 -0
  111. package/package.json +3 -2
  112. package/src/agents/bub.ts +1 -1
  113. package/src/agents/claude-code.ts +8 -5
  114. package/src/agents/codex.ts +9 -5
  115. package/src/agents/sdk-streams.test.ts +4 -4
  116. package/src/agents/sdk-streams.ts +14 -9
  117. package/src/agents/types.ts +40 -1
  118. package/src/agents/ui-message-stream.ts +1 -0
  119. package/src/cli.ts +36 -7
  120. package/src/context/types.ts +140 -0
  121. package/src/expect/index.ts +15 -8
  122. package/src/i18n/en.ts +19 -6
  123. package/src/i18n/zh-CN.ts +19 -6
  124. package/src/o11y/parsers/bub.test.ts +71 -0
  125. package/src/o11y/parsers/bub.ts +5 -0
  126. package/src/o11y/types.ts +27 -2
  127. package/src/runner/reporters/artifacts.ts +11 -3
  128. package/src/runner/reporters/live.ts +45 -8
  129. package/src/runner/run.ts +71 -21
  130. package/src/runner/types.ts +45 -2
  131. package/src/sandbox/types.ts +18 -0
  132. package/src/scoring/types.ts +5 -0
  133. package/src/shared/types.ts +3 -0
  134. package/src/util.test.ts +26 -1
  135. package/src/util.ts +16 -0
  136. package/src/view/app/App.tsx +2 -2
  137. package/src/view/app/components/AttemptModal.tsx +2 -0
  138. package/src/view/app/components/CopyControls.tsx +87 -28
  139. package/src/view/app/i18n.ts +3 -3
  140. package/src/view/client-dist/app.css +1 -1
  141. package/src/view/client-dist/app.js +18 -18
  142. package/docs/README.md +0 -120
  143. package/docs/adapters/README.md +0 -60
  144. package/docs/adapters/authoring.md +0 -179
  145. package/docs/adapters/coding-agent-skills-plugins.md +0 -324
  146. package/docs/adapters/collection.md +0 -128
  147. package/docs/adapters/contract.md +0 -264
  148. package/docs/adapters/reference/agent-eval.md +0 -215
  149. package/docs/adapters/reference/agent-loop-apis.md +0 -69
  150. package/docs/adapters/reference/claude-code-otel-telemetry.md +0 -100
  151. package/docs/adapters/reference/eve-protocol.md +0 -127
  152. package/docs/adapters/reference/otel-genai.md +0 -107
  153. package/docs/adapters/reference/otel-instrumentation.md +0 -65
  154. package/docs/adapters/targets.md +0 -99
  155. package/docs/architecture.md +0 -140
  156. package/docs/assertions.md +0 -387
  157. package/docs/capabilities-by-construction.md +0 -48
  158. package/docs/cli.md +0 -173
  159. package/docs/concepts.md +0 -106
  160. package/docs/e2e-ci.md +0 -332
  161. package/docs/eval-authoring.md +0 -319
  162. package/docs/experiments.md +0 -157
  163. package/docs/getting-started.md +0 -224
  164. package/docs/multi-agent.md +0 -145
  165. package/docs/observability.md +0 -337
  166. package/docs/origin-integration.md +0 -195
  167. package/docs/references.md +0 -35
  168. package/docs/reports.md +0 -551
  169. package/docs/results-format.md +0 -228
  170. package/docs/results-lib.md +0 -191
  171. package/docs/runner.md +0 -104
  172. package/docs/sandbox.md +0 -276
  173. package/docs/scoring.md +0 -119
  174. package/docs/source-map.md +0 -126
  175. package/docs/tier-sync.md +0 -193
  176. package/docs/view.md +0 -194
  177. package/docs/vision.md +0 -88
@@ -0,0 +1,161 @@
1
+ ---
2
+ title: "Built-in agent capabilities reference"
3
+ sidebarTitle: "Built-in Agent Capabilities"
4
+ description: "Which capabilities NiceEval's built-in claude-code, codex, and bub adapters implement, which assertions they map to, and known limits."
5
+ ---
6
+
7
+ The three built-in sandbox agents (`claude-code`, `codex`, `bub`) do not implement exactly the same capabilities — capabilities have no declaration layer and are proven entirely by construction and actual behavior, see the [capabilities reference](/reference/capabilities). This page inventories what each built-in agent does per capability, plus two built-ins for connecting AI SDK apps: the non-invasive HTTP adapter `uiMessageStreamAgent` (with HITL) and the result converter `fromAiSdk`.
8
+
9
+ ## Capability overview
10
+
11
+ | Capability | Corresponding assertions / API |
12
+ |---|---|
13
+ | Send/receive messages (base contract) | `t.send()` (can be called multiple times), `t.reply`, `turn.outputEquals` / `outputMatches`, status-based `t.succeeded()` |
14
+ | Event stream completeness | The whole scoped assertion set: `calledTool` / `toolOrder` / `usedNoTools` / `maxToolCalls` / `messageIncludes` / `noFailedActions` / `event` / `eventOrder`, etc.; **negative assertions (`notCalledTool` and friends) are only trustworthy with a complete event stream** |
15
+ | Session continuation | Cross-turn memory assertions, `t.newSession()` session isolation |
16
+ | HITL (human in the loop) | `t.parked()`, `t.requireInputRequest()`, `t.respond()` / `t.respondAll()` |
17
+ | `tracing` | `EvalResult.trace`, the call waterfall in `niceeval view` |
18
+
19
+ ## What each built-in agent implements
20
+
21
+ | Agent | Send/receive | Event stream | Session continuation | HITL | tracing | Notes |
22
+ |---|---|---|---|---|---|---|
23
+ | `claude-code` | ✅ | ✅ | ✅ (`claude --resume <id>`) | ❌ | ❌ | Built-in parser emits `compaction` events automatically; `t.event("compaction")` works |
24
+ | `codex` | ✅ | ✅ | ✅ (`codex exec resume <id>`) | ❌ | ✅ (`http/json` → OTLP) | Built-in parser emits `compaction` events automatically |
25
+ | `bub` | ✅ | ✅ | ✅ (`--session-id` + tape continuation) | ❌ | ✅ (`http/protobuf` → OTLP) | Built-in parser emits `compaction` events automatically |
26
+
27
+ All three are constructed with `defineSandboxAgent` (`Agent.kind` is always `"sandbox"`), so `t.sandbox.fileChanged()` / diff assertions, file IO, and command execution work on all three agents regardless of this table.
28
+
29
+ <Warning>
30
+ None of the three built-in sandbox agents currently support HITL: their `send` only returns `"completed"` / `"failed"`, never `"waiting"`, and never emits `input.requested` events. When you need `t.respond()` / `t.requireInputRequest()`: if the system under test is an AI SDK `useChat` backend, use the built-in `uiMessageStreamAgent` below (v7 tool approval maps natively to HITL); for anything else write your own adapter: implement the `waiting` status + `input.requested` event + resume handoff — [`examples/zh/tier1/pi-sdk`](https://github.com/CorrectRoadH/niceeval/tree/main/examples/zh/tier1/pi-sdk) / [`examples/zh/tier1/claude-sdk`](https://github.com/CorrectRoadH/niceeval/tree/main/examples/zh/tier1/claude-sdk) both have ready-made hand-written HITL references.
31
+ </Warning>
32
+
33
+ ## Per-agent details
34
+
35
+ ### claude-code
36
+
37
+ - Connection: spawns `claude --print --dangerously-skip-permissions` in the sandbox and reads back the latest transcript from `~/.claude/projects/**/*.jsonl`.
38
+ - Session continuation: appends `--resume <id>` when `ctx.session.id` has a value; the session id parsed from the transcript is written back via `ctx.session.capture()`.
39
+ - Auth: `ANTHROPIC_API_KEY` (overridable via the `apiKey` option), optional `ANTHROPIC_BASE_URL`.
40
+ - Supports `mcpServers` and `skills` (GitHub `org/repo`) options.
41
+ - Does not declare `tracing`: no OTLP receiver is started and `EvalResult.trace` is empty.
42
+
43
+ ### codex
44
+
45
+ - Connection: runs `codex exec --json` in the sandbox (`codex exec resume <id> --json` when continuing), using stdout JSONL as the transcript.
46
+ - Auth: `CODEX_API_KEY` (not `OPENAI_API_KEY`), optional `CODEX_BASE_URL` for OpenAI-compatible proxies.
47
+ - `tracing` is configured through the `[otel.trace_exporter.otlp-http]` section of `~/.codex/config.toml`, protocol `http/json`.
48
+ - Supports `mcpServers` and `skills` options.
49
+
50
+ ### bub
51
+
52
+ - Connection: runs `bub run` + `--session-id` in the sandbox and reads the tape from `~/.bub/tapes/<hash>.jsonl` as the transcript.
53
+ - Auth: `BUB_API_KEY` + `BUB_API_BASE` (OpenAI-compatible proxy).
54
+ - `tracing` is injected via environment variables (`OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`, etc.), protocol `http/protobuf`.
55
+ - Supports the `pythonPlugins` option.
56
+ - Installed via `uv tool install` (not an npm package); the first install builds a checkpoint cache to speed up later sandboxes.
57
+
58
+ ## `uiMessageStreamAgent`: built-in non-invasive adapter for AI SDK apps (with HITL)
59
+
60
+ `uiMessageStreamAgent` (exported from `niceeval/adapter`) talks non-invasively to an HTTP endpoint speaking the [UI Message Stream protocol](https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol) (the standard SSE of AI SDK `useChat` backends) — it only `fetch`es, never imports app code, so it connects wherever the app is deployed:
61
+
62
+ ```ts
63
+ import { uiMessageStreamAgent } from "niceeval/adapter";
64
+
65
+ export default uiMessageStreamAgent({
66
+ name: "my-assistant",
67
+ url: "https://my-app.example.com/api/chat",
68
+ body: (ctx) => ({ model: ctx.model }), // When the app supports request-level model selection, model comparison needs zero changes
69
+ });
70
+ ```
71
+
72
+ Capabilities it handles for you:
73
+
74
+ - **Send/receive + event stream**: SSE frames are reduced by the `ai` package's official framework-agnostic reducer `readUIMessageStream` (the same one inside `useChat`); tool calls/results/message text are built directly from message parts — **the app is not required to wire up OTel**.
75
+ - **Session continuation**: the protocol is server-stateless, "client carries the full history" — the factory stores the whole `UIMessage[]` with `ctx.session.history()` and replays it verbatim each turn; a new conversation line (after `t.newSession()`) gets an empty history.
76
+ - **HITL**: AI SDK v7 tool approval (tools with `needsApproval: true`) maps natively — when a part stops at `approval-requested`, the whole turn is `status: "waiting"` + `input.requested`; `t.respond("approve" / "deny")` is translated into `approval-responded`, rewriting that part in place and resending `messages` verbatim to trigger the server to continue (identical protocol behavior to a real frontend's `addToolApprovalResponse()` + `sendMessage()`; there is no separate approve endpoint). Denied calls land in the event stream as `rejected`, and by default carry a "do not retry" reason (`denyReason` can override) — without one, models often resend the very same call.
77
+ - **usage / waterfall**: UI Message Stream protocol frames carry no usage, so usage assertions like `t.maxTokens` have no data by default on this built-in (an app putting usage into message metadata is the app's own protocol extension). The waterfall is a separate matter: when the app has OTel instrumentation (e.g. the official `@ai-sdk/otel`), send spans to NiceEval per [OTel integration](/guides/connect-otel) and `niceeval view` gets a full waterfall — spans only feed the waterfall, never assertions.
78
+
79
+ You need `ai` installed in the eval project (optional peer dependency; the protocol reducer comes from it). Full runnable example: [`examples/zh/tier1/ai-sdk-v7`](https://github.com/CorrectRoadH/niceeval/tree/main/examples/zh/tier1/ai-sdk-v7).
80
+
81
+ ## SDK event stream converters: `fromClaudeSdkMessages` / `fromPiAgentEvents` / `fromCodexThreadEvents`
82
+
83
+ Each agent SDK's streaming protocol is a generic protocol defined by the SDK, not some app's private format — the native-frame-to-standard-event mapping knowledge lives in the official package (exported from `niceeval/adapter`), so when writing your own non-invasive adapter only the **transport glue** remains (which endpoint the app puts the stream on, which endpoint approvals go through). Driving the loop frame by frame is itself an official piece (`driveFrameStream`, see the next section), so no hand-written `for` loop:
84
+
85
+ ```ts
86
+ import { sseJsonFrames, fromClaudeSdkMessages, driveFrameStream } from "niceeval/adapter";
87
+
88
+ const frames = sseJsonFrames<SDKMessage>(res.body); // Standard SSE → per-frame JSON
89
+ const stream = fromClaudeSdkMessages(); // SDKMessage → standard events
90
+ return driveFrameStream(frames, stream, ctx); // Feed stream.add() frame by frame, summarize into a Turn
91
+ ```
92
+
93
+ - **`fromClaudeSdkMessages`** (Claude Agent SDK `SDKMessage`): `assistant` text/tool_use blocks, `user` tool_result blocks, `system`/`permission_denied` (→ `rejected`), `result` usage; `markRejected()` registers denied calls.
94
+ - **`fromPiAgentEvents`** (pi-agent-core `AgentEvent`): text/thinking/usage from `message_end`, tool pairs from `tool_execution_start/end`.
95
+ - **`fromCodexThreadEvents`** (Codex SDK `ThreadEvent`): message-class frames from `agent_message`/`reasoning`, tool items (`command_execution` / `mcp_tool_call` / `file_change` / `web_search` → `action.called` + `action.result`), usage from `turn.completed`, error frames. The waterfall needs the codex CLI's native OTLP (the `[otel]` block in config.toml) sending spans to NiceEval, normalized with the official `mapCodexSpans`.
96
+
97
+ Runnable references: the claude-sdk / pi-sdk / codex-sdk adapters under [`examples/zh/tier1`](https://github.com/CorrectRoadH/niceeval/tree/main/examples/zh/tier1) are exactly these three converters + `driveFrameStream` + transport glue.
98
+
99
+ ## Generic "assembly" pieces: `driveFrameStream` / `deltaStream` + `ctx.session`
100
+
101
+ Inside a hand-written send, only three truly independent parts exist: transport (how to send), reduce (raw data → events, handled by the converters above), and orchestration (session continuation + HITL pause/resume). The third part is protocol-agnostic, pure control-flow pattern — the accessors for session continuation (`id`/`capture`, `history`) and the HITL paused-turn scene (`hold`/`take`) hang directly off `ctx.session` (`AgentSession`, see the [Adapter concept](/concepts/adapter#context-agentcontext)); nothing to import separately and nothing to declare. What actually remains reusable across protocols is just two official pieces:
102
+
103
+ | Piece | What it solves |
104
+ |---|---|
105
+ | `driveFrameStream(cursor, reducer, ctx, onFrame?)` | Feeds the reducer frame by frame, handles transport frames, detects the HITL pause signal — the loop itself collapsed into one function. On pause the `cursor` is not closed: store it with `ctx.session.hold(cursor)` inside `onFrame`, and on the answer turn `ctx.session.take()` retrieves it and keeps reading, without issuing a new request |
106
+ | `deltaStream(spec)` | Generic accumulator for token-by-token / per-argument deltas — for protocols with "no whole-chunk landing frames" like raw OpenAI/Anthropic streaming APIs or your own hand-rolled token-by-token backend; declare "which operation this frame belongs to" (text delta / tool-argument delta / finalize) and it manages assembly timing itself |
107
+
108
+ ```ts
109
+ // First thing in send: check for a held scene (HITL answer turn)
110
+ const pending = ctx.session.take<Pending>();
111
+ if (pending) {
112
+ // ...match verdicts from input.responses, hand the approval back to the app...
113
+ return driveFrameStream(pending.cursor, pending.stream, ctx, onFrame);
114
+ }
115
+
116
+ // Normal turn: session continuation via ctx.session.id / capture; on pause, ctx.session.hold stores the cursor
117
+ return driveFrameStream(cursor, stream, ctx, (frame, derived) => {
118
+ ctx.session.capture(stream.sessionId);
119
+ const gated = derived.find((e) => e.type === "action.called" && e.name === GATED_TOOL_NAME);
120
+ if (gated?.type === "action.called") {
121
+ ctx.session.hold<Pending>({ cursor, stream, toolUseId: gated.callId });
122
+ return { pause: { id: gated.callId, action: GATED_TOOL_NAME, options: [{ id: "approve" }, { id: "deny" }] } };
123
+ }
124
+ });
125
+ ```
126
+
127
+ The three Tier 1 examples (claude-sdk and pi-sdk use `driveFrameStream` + `ctx.session.hold`/`take`; codex-sdk uses only `driveFrameStream`) have been rewritten on these pieces — the adapters are down to transport plus the "does this frame need extra handling" decision in `onFrame`, with no hand-written loops or module-level Maps.
128
+
129
+ ## `fromAiSdk`: AI SDK result → event stream converter
130
+
131
+ `fromAiSdk` (exported from `niceeval/adapter`) is used inside adapters you write yourself (e.g. server-side direct construction for an HTTP web agent, see `examples/zh/ai-sdk/`). It maps an AI SDK `generateText` / `streamText` result into `{ events, usage, status }` — exact `toolCallId` pairing, order-faithful, `tool-error` mapped to a failed `action.result`, usage aggregation (v4 / v5 / v7 field drift all covered) — laid straight into the `Turn`:
132
+
133
+ ```ts
134
+ import { defineAgent, fromAiSdk } from "niceeval/adapter";
135
+
136
+ export default defineAgent({
137
+ name: "my-ai-sdk-agent",
138
+ async send(input) {
139
+ const result = await generateText({ model, tools, prompt: input.text });
140
+ return { ...fromAiSdk(result), data: result.text };
141
+ },
142
+ });
143
+ ```
144
+
145
+ The `status` comes from the converter: `"waiting"` when there is a tool approval pending human review (with an `input.requested` event attached), otherwise `"completed"`. Session continuation (multi-turn resume), handing HITL verdicts back, and tracing depend on how you write your own `send`.
146
+
147
+ ## How to choose
148
+
149
+ - The system under test is an AI SDK app (`useChat` backend): the built-in `uiMessageStreamAgent` connects non-invasively with zero mapping (including HITL).
150
+ - The system under test is another agent system (HTTP / gRPC service): connect non-invasively and hand-write the event mapping (official converters cover most of it) — all five examples under [`examples/zh/tier1`](https://github.com/CorrectRoadH/niceeval/tree/main/examples/zh/tier1) take this path; if the app already has OTel instrumentation, wire it up for a bonus waterfall.
151
+ - You need the call waterfall (the trace in `niceeval view`): all three of `claude-code` / `codex` / `bub` emit OTLP themselves (claude-code via the CLI's beta telemetry; its spans carry structure and timing only); adapters you write yourself just declare `tracing` (see the OTel guide).
152
+ - You need human approval / multi-step confirmation (HITL): `uiMessageStreamAgent` supports it natively (AI SDK v7 tool approval); none of the three sandbox agents do; for other systems write your own adapter, assembled from `driveFrameStream` + `ctx.session.hold`/`take` (the pi-sdk / claude-sdk Tier 1 examples have ready-made patterns).
153
+ - You want to run a coding agent that edits code, inspect diffs, and judge tool calls: `claude-code` / `codex` / `bub` (send/receive + event stream + session continuation + workspace + sandbox).
154
+ - Your backend protocol is home-grown with no official SDK converter: do not hand-write send from scratch — pick a reduce shape (a small mapping for whole-chunk landing, `deltaStream` for token-by-token deltas), take `ctx.session.id`/`capture` or `ctx.session.history` for session continuation, add `ctx.session.hold`/`take` if you have HITL, and the assembly is your send.
155
+
156
+ ## Related reading
157
+
158
+ - [Connect your agent](/guides/connect-your-agent) — how to implement each capability in your own adapter and which assertions it maps to.
159
+ - [Sandbox Agent](/guides/sandbox-agent) — how to run the built-in sandbox agents and write your own.
160
+ - [defineAgent reference](/reference/define-agent) — full parameters of `defineAgent` / `defineSandboxAgent`.
161
+ - [OTel integration](/guides/connect-otel) — send your app's spans to NiceEval in exchange for the call waterfall in `niceeval view`.
@@ -0,0 +1,76 @@
1
+ ---
2
+ title: "Capabilities reference"
3
+ sidebarTitle: "Capabilities"
4
+ description: "How NiceEval knows what your adapter has done: there are no declarative capability bits — everything is proven by construction and actual behavior."
5
+ ---
6
+
7
+ Capabilities answer one question: **what has your adapter actually done** — which decides which assertions are trustworthy and which services the runner prepares for you.
8
+
9
+ There is no `capabilities` field on `defineAgent` / `defineSandboxAgent` — **nothing needs to be declared**. Every capability is proven by how the agent is constructed or by what it actually returns. The decision table is below.
10
+
11
+ ## Where capabilities come from: one decision table
12
+
13
+ | Capability | How it is obtained | What you write |
14
+ |---|---|---|
15
+ | `t.sandbox` / `t.sandbox.fileChanged()` and other filesystem assertions | Constructed with `defineSandboxAgent` (`Agent.kind` is always `"sandbox"`) | Nothing |
16
+ | `tracing` (OTLP receiver → `niceeval view` waterfall) | CLI-style agents write the `tracing` field; long-running apps configure a fixed `telemetry` port in the config | Nothing (the configuration is what you would write anyway) |
17
+ | Tool/action assertions (`t.calledTool()` etc., including negative assertions) | Use an official converter (`fromClaudeSdkMessages`, `fromAiSdk`, etc.) — the `action.*` events in the return value are exactly what you get | Nothing; with manual mapping, event completeness is decided by your mapping code |
18
+ | Cross-turn memory, `t.newSession()` isolation | Used `ctx.session` (`id`/`capture`, `history`) to continue context | Nothing — wire it up and continuation works; skip it and every turn is a fresh conversation, with no error either way |
19
+ | HITL (`t.respond()`) | `send` has returned `status: "waiting"` + `input.requested` | Nothing |
20
+
21
+ A capability you have not implemented = the corresponding events/fields never appear, and the related assertion group is unavailable (not an "error" — the assertions simply have nothing to judge from); basic assertions (text, structured output, judge) are always available.
22
+
23
+ ## Why there is no declaration layer
24
+
25
+ An earlier design had boolean declarations like `capabilities: { conversation, toolObservability }` to cover "the parts code cannot prove". That layer has been removed, because what it promised should never have been guaranteed by a declaration in the first place:
26
+
27
+ - **Construction is proof**: using `defineSandboxAgent` means the system under test edits files in a sandbox — there is no need to declare `workspace: true` on top of that.
28
+ - **Fields are proof**: writing `tracing` (how to hand the OTLP endpoint to the CLI) makes tracing real. The field itself is the evidence of use; a boolean sitting next to it would just repeat it.
29
+ - **Source is proof**: `fromAiSdk(result)` consumes `result.steps`, which the AI SDK contract guarantees is the **complete** tool-loop record, so the return value carries its own proof of a complete event stream; built-in CLI parsers work the same way. With manual mapping, whether events are complete depends on whether your mapping code covers everything — that is not something a declaration can guarantee, and declaring it would not make the code any more complete.
30
+ - **Behavior is proof**: HITL has no capability bit — `send` returns `status: "waiting"` + an `input.requested` event, so the behavior is directly observable.
31
+ - **Wire it up and it continues**: session continuation is no longer a "declared to exist" switch. `ctx.session` is always there; use the `id`/`capture` or `history` accessors and context continues, skip them and every turn is a brand-new session — both are normal states, neither errors, and the difference is purely how your adapter is actually written.
32
+
33
+ ```ts
34
+ export default defineAgent({
35
+ name: "my-ai-sdk-agent",
36
+ async send(input, ctx) {
37
+ const result = await generateText({ model, tools, prompt: input.text });
38
+ return { ...fromAiSdk(result), data: result.text };
39
+ // ↑ The return value carries complete tool events; no capability declaration needed
40
+ },
41
+ });
42
+ ```
43
+
44
+ ## What happens when a manual mapping misses events
45
+
46
+ With the declaration layer gone, "false claims" no longer exist either — whatever tool events your manual mapping misses, assertions like `t.calledTool()` see exactly the post-omission picture, and negative assertions (`notCalledTool()`) may "look like they pass" simply because the events were never written. That is not a framework misjudgment; it is a completeness problem in the manual mapping itself: **whether to trust a negative assertion depends on whether you have confirmed your mapping code covers every tool call** — there is no framework-provided middle layer vouching for you.
47
+
48
+ ## Item-by-item quick reference
49
+
50
+ ### Tool/action event completeness
51
+
52
+ Meaning: whether every tool call in the turn appears in events as `action.called` + `action.result` ([events reference](/reference/events)). There are only two sources: official converters guarantee it per the contract of the protocol under test; manual mapping is decided by your code. OTel spans are not a source — spans only feed the waterfall, never the event stream (see [OTel integration](/guides/connect-otel)).
53
+
54
+ ### Session continuation
55
+
56
+ Meaning: using `ctx.session.id` / `capture` or `ctx.session.history` to connect requests to the same backend session / accumulated history. With it, cross-turn memory assertions and `t.newSession()` isolation assertions become meaningful. Both implementation styles are shown in [Write send](/guides/write-send).
57
+
58
+ ### `tracing` — emitting OTLP traces
59
+
60
+ What the runner consumes: at runtime it starts a local OTLP receiver and hands this turn's trace context to you via `ctx.telemetry.headers`; normalized spans are drawn as a waterfall in `niceeval view` — drawing only, never feeding assertions. Source: CLI-style agents write the `tracing` field (`env` / `configure` hands the endpoint to the process under test); long-running apps configure a fixed port with `telemetry: { port }` in `niceeval.config.ts` (see [OTel integration](/guides/connect-otel)).
61
+
62
+ ### `kind: "sandbox"` — sandbox and file changes
63
+
64
+ What the runner consumes: it prepares a sandbox (Docker / Vercel / E2B) for each attempt and hands it to the adapter via `ctx.sandbox`; this unlocks diff assertions like `t.sandbox.fileChanged()`. Source: constructed with `defineSandboxAgent` — an internal discriminant field you never write.
65
+
66
+ ### Compaction visibility
67
+
68
+ Meaning: when the agent under test compacts its context, it emits a `compaction` event, making `t.event("compaction")` meaningful. Source: built-in parsers emit it automatically; a hand-written parser has it if you covered that logic.
69
+
70
+ ## Related reading
71
+
72
+ - [Events reference](/reference/events) — exactly what the event stream promises.
73
+ - [Adapter concept](/concepts/adapter) — the full `ctx`/`Turn` contract and where capabilities come from.
74
+ - [Connect your agent](/guides/connect-your-agent) — how to follow each integration path.
75
+ - [OTel integration](/guides/connect-otel) — the full walkthrough of spans feeding only the waterfall, never assertions.
76
+ - [Built-in agent capabilities](/reference/builtin-agents) — what each built-in adapter has done.
@@ -0,0 +1,120 @@
1
+ ---
2
+ title: "NiceEval CLI: commands, flags, and exit codes reference"
3
+ sidebarTitle: "CLI"
4
+ description: "NiceEval CLI reference for exp, init, list, clean, and view, plus experiment selection, eval filtering, sandbox, concurrency, budget, and JUnit CI output."
5
+ ---
6
+
7
+ The NiceEval CLI is the entry point for discovery, execution, and result viewing. Actual eval execution follows an experiment-first model: **`exp` selects a commit-ready run configuration**, and only the positional arguments after the experiment filter eval IDs by prefix. Agent, model, and feature flags live in `experiments/`, not in temporary CLI flags.
8
+
9
+ ## Commands
10
+
11
+ <CardGroup cols={2}>
12
+ <Card title="npx niceeval exp &lt;group&gt; [id-prefix]" icon="flask">
13
+ Run a named experiment group or config. Optional trailing positional args filter by eval ID prefix.
14
+ </Card>
15
+ <Card title="npx niceeval init" icon="wand-magic-sparkles">
16
+ Generate `evals/`, `niceeval.config.ts`, and sample files.
17
+ </Card>
18
+ <Card title="npx niceeval list" icon="list">
19
+ Discover and print all evals without running them.
20
+ </Card>
21
+ <Card title="npx niceeval clean" icon="trash">
22
+ Remove historical `.niceeval/` artifacts.
23
+ </Card>
24
+ <Card title="npx niceeval view" icon="eye">
25
+ Open the result viewer for the most recent run.
26
+ </Card>
27
+ </CardGroup>
28
+
29
+ ## Output language
30
+
31
+ NiceEval supports localized CLI and runtime copy without configuration in `niceeval.config.ts`. When you need fixed output language, use environment variables:
32
+
33
+ ```bash
34
+ NICEEVAL_LANG=en npx niceeval list
35
+ NICEEVAL_LANG=zh-CN npx niceeval list
36
+ ```
37
+
38
+ Detection order is `NICEEVAL_LANG`, `NICEEVAL_LOCALE`, `LC_ALL`, `LC_MESSAGES`, then `LANG`. Values starting with `zh` use `zh-CN`; everything else uses `en`. If nothing is set, the default is `zh-CN`. This affects terminal/runtime copy only. It does not change machine fields in result JSON or translate judge prompts.
39
+
40
+ ## `npx niceeval exp [group|config] [id-prefix...]`
41
+
42
+ ```bash
43
+ # Run every experiment under experiments/
44
+ npx niceeval exp
45
+
46
+ # Run one experiment group
47
+ npx niceeval exp compare-models
48
+
49
+ # Inside that group, only run evals whose ID starts with weather
50
+ npx niceeval exp compare-models weather
51
+ ```
52
+
53
+ <Note>
54
+ Eval filters only appear after the experiment selector. Bare `npx niceeval weather` does not run. Use `npx niceeval exp local weather` or `npx niceeval exp compare weather`.
55
+ </Note>
56
+
57
+ ## Common flags
58
+
59
+ <ParamField query="--agent" type="string">
60
+ Experiment runs do not support this flag. To switch agents, add or duplicate a config file in `experiments/`.
61
+ </ParamField>
62
+
63
+ > There is no CLI flag for selecting a sandbox backend. Backend choice only lives in code: set `sandbox` on the experiment (`defineExperiment`) or in `niceeval.config.ts` (`defineConfig`) as a project-level fallback, using `dockerSandbox()` / `vercelSandbox()` / `e2bSandbox()` from `niceeval/sandbox`.
64
+
65
+ <ParamField query="--model" type="string">
66
+ Experiment runs do not support this flag. To switch models, add or duplicate an experiment file and change `model`.
67
+ </ParamField>
68
+
69
+ <ParamField query="--max-concurrency" type="number">
70
+ Set how many evals may run at the same time.
71
+ </ParamField>
72
+
73
+ <ParamField query="--runs" type="number">
74
+ Set how many times each eval runs, commonly for pass@N.
75
+ </ParamField>
76
+
77
+ <ParamField query="--early-exit" type="boolean">
78
+ Stop the remaining attempts for an eval after one attempt passes.
79
+ </ParamField>
80
+
81
+ <ParamField query="--timeout-ms" type="number">
82
+ Timeout for one eval.
83
+ </ParamField>
84
+
85
+ <ParamField query="--budget" type="number">
86
+ Budget limit for the whole run.
87
+ </ParamField>
88
+
89
+ <ParamField query="--strict" type="boolean">
90
+ Recommended in CI. Make failures show up more explicitly in the exit code.
91
+ </ParamField>
92
+
93
+ ## `list`
94
+
95
+ ```bash
96
+ npx niceeval list
97
+ ```
98
+
99
+ Use this to confirm eval discovery, IDs, and config loading.
100
+
101
+ ## `exp`
102
+
103
+ ```bash
104
+ npx niceeval exp compare-models
105
+ npx niceeval exp compare-models weather-tool
106
+ ```
107
+
108
+ Run a named experiment and compare agents, models, or flags. Positional args after the experiment filter eval IDs by prefix.
109
+
110
+ ## `view`
111
+
112
+ ```bash
113
+ npx niceeval view
114
+ ```
115
+
116
+ Open the local result viewer. By default it shows the latest run under `.niceeval/`.
117
+
118
+ ## Exit codes
119
+
120
+ The CI rule is simple: when a gate fails or the run itself fails, the command should exit non-zero. When everything passes, it should exit 0. Use `--strict` when you want the stricter CI behavior explicitly.
@@ -0,0 +1,168 @@
1
+ ---
2
+ title: "defineAgent and defineSandboxAgent: adapter reference"
3
+ sidebarTitle: "defineAgent"
4
+ description: "Reference for defineAgent and defineSandboxAgent: AgentContext, AgentSession, the Sandbox interface, and shared sandbox helpers."
5
+ ---
6
+
7
+ Every NiceEval agent is an adapter: code that knows how to drive a specific backend and translate its output into the standard event stream. The runner only calls `agent.send(input, ctx)`.
8
+
9
+ ![Full round trip of one t.send: the eval calls t.send, the runner assembles TurnInput and ctx, the adapter returns a Turn with standard events.](/images/agent-turn-roundtrip-en.svg)
10
+
11
+ ## `defineAgent`
12
+
13
+ Use this for direct agent integrations:
14
+
15
+ ```ts
16
+ import { defineAgent } from "niceeval/adapter";
17
+ ```
18
+
19
+ <ParamField body="name" type="string" required>
20
+ Unique agent name. Experiments reference the agent object, and reports group results by this name.
21
+ </ParamField>
22
+
23
+ <ParamField body="tracing" type="AgentTracing">
24
+ OTLP export configuration, used by CLI / child-process agents: declares how to hand the receiver endpoint to the process under test (`env` / `configure`). Long-running HTTP apps do not need this field — configure a fixed port with `telemetry: { port }` in `niceeval.config.ts` instead (see [OTel integration](/guides/connect-otel)). Spans only feed the `niceeval view` waterfall, never assertions.
25
+ </ParamField>
26
+
27
+ <ParamField body="spanMapper" type="(spans: TraceSpan[]) => TraceSpan[]">
28
+ Thin mapper from native spans to canonical GenAI semconv; it only affects semantic coloring in the waterfall. Omit it to fall back to the generic heuristic. For what maps to what in the waterfall and how to write your own (`tagSpan` / `heuristicTag`), see [OTel integration](/guides/connect-otel).
29
+ </ParamField>
30
+
31
+ <ParamField body="send" type="(input: TurnInput, ctx: AgentContext) => Promise<Turn>" required>
32
+ Core function that drives the agent. Must return a standard `Turn`.
33
+ </ParamField>
34
+
35
+ `Agent.kind` produced by `defineAgent` is always `"remote"` (an internal discriminant field you never declare). There are no capability bits on `t` that need declaring to unlock — filesystem assertions like `t.sandbox` exist only on agents constructed with `defineSandboxAgent` (`kind: "sandbox"`); everything else is judged by which events `send` actually returned and whether `ctx.session` was used. See the [capabilities reference](/reference/capabilities).
36
+
37
+ ## `input` (`TurnInput`)
38
+
39
+ <ResponseField name="text" type="string">
40
+ Text passed by the current `t.send(...)`.
41
+ </ResponseField>
42
+
43
+ <ResponseField name="files" type="readonly InputFile[] | undefined">
44
+ Files attached to this turn (images, etc.). Adapters that do not support multimodal input can ignore it.
45
+ </ResponseField>
46
+
47
+ <ResponseField name="responses" type="readonly InputResponse[] | undefined">
48
+ Present only on answer turns (`t.respond` / `t.respondAll`): per-request structured answers, matched by `requestId`.
49
+ </ResponseField>
50
+
51
+ ## `ctx` (`AgentContext`)
52
+
53
+ <ResponseField name="signal" type="AbortSignal">
54
+ For timeouts and cancellation; pass it through to every request you make.
55
+ </ResponseField>
56
+
57
+ <ResponseField name="model" type="string | undefined">
58
+ Model comparison for Tier 1: `experiment.model`, passed through; forward it if the app's interface accepts a model selection.
59
+ </ResponseField>
60
+
61
+ <ResponseField name="flags" type="Readonly<Record<string, unknown>>">
62
+ Feature A/B for Tier 3: `experiment.flags`, passed through; `{}` when not configured.
63
+ </ResponseField>
64
+
65
+ <ResponseField name="telemetry" type="Telemetry | undefined">
66
+ Present only when OTel integration is configured: this turn's W3C trace context (`headers`, a fresh `traceparent` per turn).
67
+ </ResponseField>
68
+
69
+ <ResponseField name="sandbox" type="Sandbox | undefined">
70
+ Only agents constructed with `defineSandboxAgent` receive this.
71
+ </ResponseField>
72
+
73
+ <ResponseField name="session" type="AgentSession">
74
+ State slot for the current conversation line. Every `send` on the same conversation line gets the same `ctx.session`; a new conversation line (the eval's first turn, or after `t.newSession()`) gets a fresh one:
75
+
76
+ - `id?: string` / `capture(id): void` — session continuation when the server keeps history: `id` is the session id recorded on this line (`undefined` on a new line), `capture` records the returned id (it only lands when none has been recorded yet).
77
+ - `history<TMsg>(): { get(): TMsg[]; commit(messages: TMsg[]): void }` — session continuation when the client carries the full history.
78
+ - `hold<T>(state: T): void` / `take<T>(): T | undefined` — storage for the HITL paused-turn scene; `take` clears on read (consume once).
79
+ - `state: Record<string, unknown>` — escape hatch, starts as `{}`, never written by the framework.
80
+
81
+ Full contract in the [Adapter concept](/concepts/adapter#context-agentcontext).
82
+ </ResponseField>
83
+
84
+ <ResponseField name="log" type="(msg: string) => void">
85
+ Writes a message to the run log, visible in `niceeval view`.
86
+ </ResponseField>
87
+
88
+ ## Direct agent example
89
+
90
+ ```ts
91
+ export default defineAgent({
92
+ name: "echo",
93
+ async send(input) {
94
+ return {
95
+ status: "completed",
96
+ events: [{ type: "message", role: "assistant", text: input.text }],
97
+ };
98
+ },
99
+ });
100
+ ```
101
+
102
+ ## `defineSandboxAgent`
103
+
104
+ Use this for coding agent CLIs. The produced `Agent.kind` is always `"sandbox"` — filesystem assertions like `t.sandbox` and `t.sandbox.fileChanged()` unlock only on this kind of agent. `ctx.sandbox` is the handle to the current isolated environment:
105
+
106
+ <ResponseField name="runCommand(cmd, args?, opts?)" type="(cmd: string, args?: string[], opts?: CommandOptions) => Promise<CommandResult>">
107
+ Run a command.
108
+ </ResponseField>
109
+
110
+ <ResponseField name="runShell(script, opts?)" type="(script: string, opts?: CommandOptions) => Promise<CommandResult>">
111
+ Run a shell script.
112
+ </ResponseField>
113
+
114
+ <ResponseField name="readFile(path)" type="(path: string) => Promise<string>">
115
+ Read a file inside the sandbox.
116
+ </ResponseField>
117
+
118
+ <ResponseField name="fileExists(path)" type="(path: string) => Promise<boolean>">
119
+ Check whether a file exists inside the sandbox.
120
+ </ResponseField>
121
+
122
+ <ResponseField name="writeFiles(files, targetDir?)" type="(files: Record<string, string>, targetDir?: string) => Promise<void>">
123
+ Write file contents by path.
124
+ </ResponseField>
125
+
126
+ <ResponseField name="uploadFiles(files, targetDir?)" type="(files: SandboxFile[], targetDir?: string) => Promise<void>">
127
+ Upload local files into the sandbox.
128
+ </ResponseField>
129
+
130
+ <ResponseField name="uploadDirectory(localDir, targetDir?, opts?)" type="(localDir: string, targetDir?: string, opts?: { ignore?: string[] }) => Promise<void>">
131
+ Upload an entire local directory, typically used to write the eval's starting files.
132
+ </ResponseField>
133
+
134
+ <ResponseField name="sandboxId" type="string">
135
+ Id of the current sandbox instance.
136
+ </ResponseField>
137
+
138
+ <ResponseField name="stop()" type="() => Promise<void>">
139
+ Stop the sandbox.
140
+ </ResponseField>
141
+
142
+ ```ts
143
+ import { defineSandboxAgent } from "niceeval/adapter";
144
+
145
+ export default defineSandboxAgent({
146
+ name: "my-cli-agent",
147
+ async send(input, ctx) {
148
+ await ctx.sandbox.runCommand("my-agent", ["run", input.text]);
149
+ return { status: "completed", events: [] };
150
+ },
151
+ });
152
+ ```
153
+
154
+ ## Registration
155
+
156
+ ```ts
157
+ import { defineExperiment } from "niceeval";
158
+ import echo from "./agents/echo";
159
+
160
+ export default defineExperiment({
161
+ agent: echo,
162
+ runs: 1,
163
+ });
164
+ ```
165
+
166
+ ## `ctx` and `t`
167
+
168
+ `ctx` is the run context seen by the adapter side; `t` is the test context seen by the eval author. They use the same run data but have different responsibilities.
@@ -0,0 +1,41 @@
1
+ ---
2
+ title: "defineConfig: project defaults"
3
+ sidebarTitle: "defineConfig"
4
+ description: "defineConfig reference: judge, reporters, concurrency, timeout, and sandbox defaults."
5
+ ---
6
+
7
+ Use `defineConfig` in `niceeval.config.ts` for project-wide defaults. Agent, model, flags, runs, and per-experiment budget belong in `experiments/` files via `defineExperiment`.
8
+
9
+ ```ts
10
+ import { defineConfig } from "niceeval";
11
+ import { JUnit } from "niceeval/reporters";
12
+
13
+ export default defineConfig({
14
+ judge: { model: "gpt-5.4-mini" },
15
+ reporters: [JUnit(".niceeval/junit.xml")],
16
+ maxConcurrency: 4,
17
+ timeoutMs: 300_000,
18
+ });
19
+ ```
20
+
21
+ <ParamField body="judge" type="{ model: string; baseUrl?: string; apiKeyEnv?: string }">
22
+ Default judge configuration for `t.judge.autoevals.*`. Individual evals can override this with `defineEval({ judge })`.
23
+ </ParamField>
24
+
25
+ <ParamField body="reporters" type="Reporter[]">
26
+ Additional reporters that observe the whole run, such as `Braintrust(...)` for uploading to an experiment platform. See [Reporters](../guides/reporters). For temporary JUnit output, use the CLI flag `--junit <path>`.
27
+ </ParamField>
28
+
29
+ <ParamField body="maxConcurrency" type="number">
30
+ Maximum concurrent attempts. Sandbox backends may choose a lower default when this is omitted.
31
+ </ParamField>
32
+
33
+ <ParamField body="timeoutMs" type="number">
34
+ Per-attempt timeout in milliseconds.
35
+ </ParamField>
36
+
37
+ <ParamField body="sandbox" type="SandboxSpec">
38
+ Default sandbox backend for sandbox agents, built with `dockerSandbox()` / `vercelSandbox()` / `e2bSandbox()` from `niceeval/sandbox` — bare strings like `"docker"` are no longer accepted here. Required for sandbox agents (directly on the experiment, or via this project-wide fallback: `exp.sandbox ?? config.sandbox`) — niceeval no longer auto-detects a backend from env vars, and there is no CLI flag for choosing one.
39
+ </ParamField>
40
+
41
+ niceeval keeps environment preparation in ordinary code. Prepare files inside `test(t)` with `t.sandbox.writeFiles`, `uploadFiles`, or `uploadDirectory`; put adapter setup in the Adapter.