niceeval 0.6.0 → 0.6.2

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 (314) hide show
  1. package/dist/agents/types.d.ts +72 -6
  2. package/dist/context/types.d.ts +32 -12
  3. package/dist/i18n/en.d.ts +54 -0
  4. package/dist/i18n/zh-CN.d.ts +55 -1
  5. package/dist/o11y/types.d.ts +16 -2
  6. package/dist/report/aggregate.d.ts +5 -3
  7. package/dist/report/aggregate.js +32 -5
  8. package/dist/report/built-ins/experiment-comparison.d.ts +39 -0
  9. package/dist/report/built-ins/experiment-comparison.js +119 -0
  10. package/dist/report/built-ins/index.d.ts +2 -1
  11. package/dist/report/built-ins/index.js +2 -2
  12. package/dist/report/components.d.ts +10 -2
  13. package/dist/report/components.js +3 -3
  14. package/dist/report/compute.d.ts +11 -18
  15. package/dist/report/compute.js +68 -66
  16. package/dist/report/flag.d.ts +16 -1
  17. package/dist/report/flag.js +19 -1
  18. package/dist/report/format.d.ts +16 -14
  19. package/dist/report/format.js +28 -30
  20. package/dist/report/index.d.ts +5 -4
  21. package/dist/report/index.js +6 -5
  22. package/dist/report/locale.d.ts +23 -3
  23. package/dist/report/locale.js +47 -6
  24. package/dist/report/metrics.d.ts +13 -1
  25. package/dist/report/metrics.js +66 -15
  26. package/dist/report/primitives.d.ts +6 -0
  27. package/dist/report/react/AttemptList.d.ts +4 -4
  28. package/dist/report/react/AttemptList.js +8 -10
  29. package/dist/report/react/EvalList.d.ts +1 -1
  30. package/dist/report/react/EvalList.js +0 -0
  31. package/dist/report/react/ExperimentComparison.d.ts +8 -0
  32. package/dist/report/react/ExperimentComparison.js +11 -0
  33. package/dist/report/react/ExperimentList.d.ts +4 -2
  34. package/dist/report/react/ExperimentList.js +57 -7
  35. package/dist/report/react/MetricScatter.js +6 -16
  36. package/dist/report/react/chart-math.d.ts +23 -6
  37. package/dist/report/react/chart-math.js +71 -19
  38. package/dist/report/react/fixtures.d.ts +3 -3
  39. package/dist/report/react/fixtures.js +30 -18
  40. package/dist/report/react/format.d.ts +1 -1
  41. package/dist/report/react/format.js +1 -1
  42. package/dist/report/react/index.d.ts +1 -1
  43. package/dist/report/report.d.ts +5 -1
  44. package/dist/report/report.js +6 -2
  45. package/dist/report/text/faces.d.ts +1 -1
  46. package/dist/report/text/faces.js +100 -61
  47. package/dist/report/text/table.js +36 -5
  48. package/dist/report/types.d.ts +40 -34
  49. package/dist/results/types.d.ts +11 -0
  50. package/dist/runner/feedback/sink.d.ts +110 -0
  51. package/dist/runner/types.d.ts +513 -22
  52. package/dist/sandbox/docker.d.ts +23 -2
  53. package/dist/sandbox/e2b.d.ts +15 -1
  54. package/dist/sandbox/errors.d.ts +30 -3
  55. package/dist/sandbox/io-retry.d.ts +17 -0
  56. package/dist/sandbox/registry.d.ts +2 -0
  57. package/dist/sandbox/resolve.d.ts +18 -5
  58. package/dist/sandbox/retry.d.ts +11 -1
  59. package/dist/sandbox/types.d.ts +39 -5
  60. package/dist/sandbox/vercel.d.ts +7 -1
  61. package/dist/scoring/coverage.d.ts +30 -0
  62. package/dist/scoring/display.d.ts +21 -0
  63. package/dist/scoring/display.js +120 -0
  64. package/dist/scoring/types.d.ts +103 -20
  65. package/dist/shared/aggregate.d.ts +1 -0
  66. package/dist/shared/aggregate.js +3 -3
  67. package/dist/shared/types.d.ts +28 -0
  68. package/dist/tty-line.d.ts +0 -4
  69. package/dist/util.d.ts +23 -0
  70. package/docs-site/zh/concepts/adapter.mdx +24 -6
  71. package/docs-site/zh/concepts/assert.mdx +11 -10
  72. package/docs-site/zh/concepts/evals.mdx +7 -6
  73. package/docs-site/zh/concepts/experiment.mdx +1 -1
  74. package/docs-site/zh/concepts/overview.mdx +7 -7
  75. package/docs-site/zh/guides/agent-feedback-loop.mdx +35 -31
  76. package/docs-site/zh/guides/authoring.mdx +33 -0
  77. package/docs-site/zh/guides/ci-integration.mdx +23 -12
  78. package/docs-site/zh/guides/connect-your-agent.mdx +29 -3
  79. package/docs-site/zh/guides/custom-reports.mdx +29 -34
  80. package/docs-site/zh/guides/dataset-fanout.mdx +25 -3
  81. package/docs-site/zh/guides/debug-sandbox.mdx +57 -0
  82. package/docs-site/zh/guides/debugging.mdx +210 -0
  83. package/docs-site/zh/guides/experiments.mdx +10 -3
  84. package/docs-site/zh/guides/fixtures.mdx +3 -1
  85. package/docs-site/zh/guides/official-adapters.mdx +27 -3
  86. package/docs-site/zh/guides/publish-report.mdx +30 -16
  87. package/docs-site/zh/guides/report-components.mdx +49 -37
  88. package/docs-site/zh/guides/reporters.mdx +2 -2
  89. package/docs-site/zh/guides/results-data.mdx +42 -8
  90. package/docs-site/zh/guides/runner.mdx +17 -7
  91. package/docs-site/zh/guides/sandbox-agent.mdx +57 -7
  92. package/docs-site/zh/guides/sandbox-providers.mdx +258 -10
  93. package/docs-site/zh/guides/scoring-guide.mdx +4 -4
  94. package/docs-site/zh/guides/viewing-results.mdx +85 -41
  95. package/docs-site/zh/guides/write-experiment.mdx +5 -3
  96. package/docs-site/zh/guides/write-send.mdx +19 -2
  97. package/docs-site/zh/index.mdx +1 -1
  98. package/docs-site/zh/reference/builtin-agents.mdx +27 -0
  99. package/docs-site/zh/reference/capabilities.mdx +2 -2
  100. package/docs-site/zh/reference/cli.mdx +35 -9
  101. package/docs-site/zh/reference/define-agent.mdx +60 -5
  102. package/docs-site/zh/reference/define-config.mdx +1 -1
  103. package/docs-site/zh/reference/define-eval.mdx +42 -9
  104. package/docs-site/zh/reference/events.mdx +2 -2
  105. package/docs-site/zh/reference/expect.mdx +36 -6
  106. package/package.json +5 -1
  107. package/src/agents/ai-sdk-otel.test.ts +1 -0
  108. package/src/agents/ai-sdk.test.ts +3 -0
  109. package/src/agents/ai-sdk.ts +3 -0
  110. package/src/agents/bub-install-spec.test.ts +34 -0
  111. package/src/agents/bub-install-spec.ts +32 -0
  112. package/src/agents/bub.ts +31 -32
  113. package/src/agents/claude-code.test.ts +130 -9
  114. package/src/agents/claude-code.ts +76 -4
  115. package/src/agents/codex.test.ts +189 -40
  116. package/src/agents/codex.ts +155 -14
  117. package/src/agents/coding-cli-versions.test.ts +15 -0
  118. package/src/agents/coding-cli-versions.ts +3 -0
  119. package/src/agents/index.ts +11 -0
  120. package/src/agents/langgraph.test.ts +204 -0
  121. package/src/agents/langgraph.ts +495 -0
  122. package/src/agents/marketplace.ts +85 -0
  123. package/src/agents/native-config.test.ts +179 -0
  124. package/src/agents/native-config.ts +267 -0
  125. package/src/agents/openai-compat.test.ts +1 -0
  126. package/src/agents/openclaw.test.ts +31 -0
  127. package/src/agents/openclaw.ts +171 -0
  128. package/src/agents/plugin-config.test.ts +1 -0
  129. package/src/agents/sdk-streams.test.ts +79 -0
  130. package/src/agents/sdk-streams.ts +55 -10
  131. package/src/agents/skills.test.ts +1 -0
  132. package/src/agents/streaming.test.ts +3 -9
  133. package/src/agents/types.ts +73 -6
  134. package/src/agents/ui-message-stream.test.ts +3 -0
  135. package/src/cli.ts +411 -108
  136. package/src/context/context.test.ts +51 -12
  137. package/src/context/context.ts +161 -29
  138. package/src/context/session.test.ts +1 -0
  139. package/src/context/session.ts +114 -6
  140. package/src/context/types.ts +30 -12
  141. package/src/define.test.ts +13 -8
  142. package/src/define.ts +25 -4
  143. package/src/expect/index.ts +53 -23
  144. package/src/i18n/en.ts +65 -4
  145. package/src/i18n/zh-CN.ts +66 -4
  146. package/src/o11y/cost.test.ts +1 -0
  147. package/src/o11y/execution-tree.test.ts +1 -20
  148. package/src/o11y/otlp/mappers/claude-code.test.ts +1 -0
  149. package/src/o11y/otlp/parse.test.ts +1 -0
  150. package/src/o11y/otlp/turn-otel.test.ts +1 -0
  151. package/src/o11y/parsers/bub.test.ts +1 -0
  152. package/src/o11y/parsers/claude-code.test.ts +1 -34
  153. package/src/o11y/parsers/openclaw.test.ts +154 -0
  154. package/src/o11y/parsers/openclaw.ts +310 -0
  155. package/src/o11y/prices.json +746 -311
  156. package/src/o11y/tool-names.test.ts +1 -0
  157. package/src/o11y/types.ts +16 -2
  158. package/src/report/aggregate.ts +34 -5
  159. package/src/report/built-in-user-parity.test.tsx +127 -173
  160. package/src/report/built-ins/experiment-comparison.tsx +179 -0
  161. package/src/report/built-ins/index.ts +7 -2
  162. package/src/report/components.tsx +11 -3
  163. package/src/report/compute.ts +80 -74
  164. package/src/report/dual-render.test.tsx +222 -91
  165. package/src/report/flag.ts +30 -2
  166. package/src/report/format.ts +36 -27
  167. package/src/report/index.ts +23 -6
  168. package/src/report/locale.ts +49 -6
  169. package/src/report/metrics.ts +68 -15
  170. package/src/report/primitives.tsx +6 -0
  171. package/src/report/react/AttemptList.tsx +9 -36
  172. package/src/report/react/EvalList.tsx +0 -0
  173. package/src/report/react/ExperimentComparison.tsx +68 -0
  174. package/src/report/react/ExperimentList.tsx +173 -55
  175. package/src/report/react/MetricScatter.tsx +13 -25
  176. package/src/report/react/chart-math.test.ts +85 -0
  177. package/src/report/react/chart-math.ts +101 -22
  178. package/src/report/react/enhance.js +72 -1
  179. package/src/report/react/fixtures.ts +34 -21
  180. package/src/report/react/format.ts +1 -1
  181. package/src/report/react/index.tsx +0 -1
  182. package/src/report/react/render.test.tsx +30 -69
  183. package/src/report/react/styles.css +112 -14
  184. package/src/report/report.test.ts +308 -105
  185. package/src/report/report.ts +6 -2
  186. package/src/report/text/faces.ts +111 -67
  187. package/src/report/text/table.ts +42 -5
  188. package/src/report/types.ts +42 -34
  189. package/src/results/annotated-source.test.ts +62 -9
  190. package/src/results/annotated-source.ts +64 -6
  191. package/src/results/attempt-evidence.test.ts +9 -7
  192. package/src/results/attempt-evidence.ts +15 -8
  193. package/src/results/attempt-source.ts +6 -3
  194. package/src/results/copy.ts +145 -55
  195. package/src/results/host-equivalence.test.ts +11 -9
  196. package/src/results/index.ts +2 -0
  197. package/src/results/locator.test.ts +1 -22
  198. package/src/results/open.ts +7 -1
  199. package/src/results/publish.ts +149 -0
  200. package/src/results/results.test.ts +85 -51
  201. package/src/results/truncate.ts +90 -0
  202. package/src/results/types.ts +7 -0
  203. package/src/results/writer.ts +31 -13
  204. package/src/runner/attempt.test.ts +138 -7
  205. package/src/runner/attempt.ts +603 -104
  206. package/src/runner/discover.test.ts +47 -0
  207. package/src/runner/discover.ts +36 -2
  208. package/src/runner/eval-source.test.ts +1 -27
  209. package/src/runner/feedback/agent.test.ts +504 -0
  210. package/src/runner/feedback/agent.ts +409 -0
  211. package/src/runner/feedback/ci.test.ts +562 -0
  212. package/src/runner/feedback/ci.ts +401 -0
  213. package/src/runner/feedback/coordinator.test.ts +317 -0
  214. package/src/runner/feedback/coordinator.ts +397 -0
  215. package/src/runner/feedback/failure.ts +40 -0
  216. package/src/runner/feedback/human.test.ts +616 -0
  217. package/src/runner/feedback/human.ts +535 -0
  218. package/src/runner/feedback/index.ts +66 -0
  219. package/src/runner/feedback/io.ts +78 -0
  220. package/src/runner/feedback/profile.test.ts +50 -0
  221. package/src/runner/feedback/profile.ts +58 -0
  222. package/src/runner/feedback/reducer.test.ts +395 -0
  223. package/src/runner/feedback/reducer.ts +260 -0
  224. package/src/runner/feedback/renderer.ts +82 -0
  225. package/src/runner/feedback/sink.ts +203 -0
  226. package/src/runner/feedback/testing.ts +106 -0
  227. package/src/runner/ledger.test.ts +230 -0
  228. package/src/runner/ledger.ts +329 -0
  229. package/src/runner/report.test.ts +128 -3
  230. package/src/runner/report.ts +33 -9
  231. package/src/runner/reporters/artifacts.ts +8 -2
  232. package/src/runner/reporters/braintrust.test.ts +8 -7
  233. package/src/runner/reporters/braintrust.ts +9 -2
  234. package/src/runner/reporters/index.ts +2 -2
  235. package/src/runner/reporters/json.test.ts +162 -0
  236. package/src/runner/reporters/json.ts +35 -8
  237. package/src/runner/reporters/shared.ts +1 -5
  238. package/src/runner/run.test.ts +760 -3
  239. package/src/runner/run.ts +242 -36
  240. package/src/runner/sandbox-prep.ts +3 -42
  241. package/src/runner/timing.ts +158 -0
  242. package/src/runner/types.ts +518 -22
  243. package/src/sandbox/checkpoint.test.ts +55 -0
  244. package/src/sandbox/checkpoint.ts +29 -8
  245. package/src/sandbox/cli-commands.ts +407 -0
  246. package/src/sandbox/docker.ts +115 -16
  247. package/src/sandbox/e2b-agent-template.test.ts +56 -0
  248. package/src/sandbox/e2b-agent-template.ts +94 -0
  249. package/src/sandbox/e2b.ts +74 -9
  250. package/src/sandbox/errors.ts +111 -4
  251. package/src/sandbox/index.ts +2 -0
  252. package/src/sandbox/io-retry.test.ts +58 -0
  253. package/src/sandbox/io-retry.ts +45 -0
  254. package/src/sandbox/keep-registry.test.ts +86 -0
  255. package/src/sandbox/keep-registry.ts +142 -0
  256. package/src/sandbox/keep.ts +178 -0
  257. package/src/sandbox/paths.test.ts +1 -0
  258. package/src/sandbox/paths.ts +19 -8
  259. package/src/sandbox/registry.ts +20 -3
  260. package/src/sandbox/resolve.ts +76 -11
  261. package/src/sandbox/retry.test.ts +70 -0
  262. package/src/sandbox/retry.ts +46 -4
  263. package/src/sandbox/types.ts +44 -6
  264. package/src/sandbox/vercel.ts +43 -20
  265. package/src/scoring/collector.ts +60 -17
  266. package/src/scoring/coverage.ts +95 -0
  267. package/src/scoring/diff.ts +81 -0
  268. package/src/scoring/display.test.ts +121 -0
  269. package/src/scoring/display.ts +133 -0
  270. package/src/scoring/evidence.test.ts +189 -0
  271. package/src/scoring/judge.test.ts +142 -0
  272. package/src/scoring/judge.ts +15 -18
  273. package/src/scoring/scoped.ts +217 -50
  274. package/src/scoring/types.ts +117 -20
  275. package/src/scoring/verdict.ts +16 -4
  276. package/src/shared/aggregate.ts +3 -2
  277. package/src/shared/types.ts +31 -0
  278. package/src/show/compose.ts +2 -2
  279. package/src/show/index.ts +29 -16
  280. package/src/show/render.ts +626 -308
  281. package/src/show/show.test.ts +251 -36
  282. package/src/tty-line.ts +8 -26
  283. package/src/util.test.ts +1 -0
  284. package/src/util.ts +41 -0
  285. package/src/view/app/components/AttemptModal.tsx +153 -2
  286. package/src/view/app/components/CodeView.tsx +32 -11
  287. package/src/view/app/components/CopyControls.tsx +2 -2
  288. package/src/view/app/i18n.ts +6 -0
  289. package/src/view/app/lib/attempt-route.test.ts +1 -0
  290. package/src/view/app/lib/verdict.ts +7 -9
  291. package/src/view/artifact-serving.test.ts +2 -1
  292. package/src/view/client-dist/app.css +1 -1
  293. package/src/view/client-dist/app.js +17 -17
  294. package/src/view/data.test.ts +2 -1
  295. package/src/view/data.ts +17 -7
  296. package/src/view/index.ts +12 -1
  297. package/src/view/server.ts +2 -0
  298. package/src/view/shared/types.ts +1 -1
  299. package/src/view/styles.css +3 -0
  300. package/src/view/view-report.test.ts +11 -10
  301. package/dist/o11y/execution-tree.d.ts +0 -103
  302. package/dist/o11y/otlp/select.d.ts +0 -22
  303. package/dist/report/built-ins/cost-pass-rate-comparison.d.ts +0 -1
  304. package/dist/report/built-ins/cost-pass-rate-comparison.js +0 -17
  305. package/dist/results/annotated-source.d.ts +0 -61
  306. package/dist/results/attempt-evidence.d.ts +0 -69
  307. package/dist/results/attempt-source.d.ts +0 -15
  308. package/src/report/built-ins/cost-pass-rate-comparison.tsx +0 -23
  309. package/src/runner/reporters/console.ts +0 -70
  310. package/src/runner/reporters/live.test.ts +0 -56
  311. package/src/runner/reporters/live.ts +0 -247
  312. package/src/runner/reporters/quiet.test.ts +0 -66
  313. package/src/runner/reporters/quiet.ts +0 -49
  314. package/src/runner/reporters/table.ts +0 -277
@@ -0,0 +1,495 @@
1
+ // LangGraph 官方 event streaming 协议 → 标准 StreamEvent 的官方转换器。
2
+ //
3
+ // 定位:LangGraph 可以进程内运行,也可以部署在自建 HTTP 服务或 Agent Server 后——
4
+ // 本文件只认协议帧本身,不绑定任何 transport,也不提供 langGraphAgent() 工厂
5
+ // (契约见 docs/feature/adapters/sdk/langgraph/README.md)。会话操作属于应用 adapter:
6
+ // `thread_id` 由 adapter 写回 `ctx.session.id`,`input.responses` 由 adapter 按应用协议
7
+ // 翻译成 `Command(resume=...)`,都不进转换器。类型全部用结构化 *Like 声明(同
8
+ // sdk-streams.ts 的先例),不依赖 LangGraph SDK 包。
9
+ //
10
+ // 用法(以 SSE transport 为例):
11
+ //
12
+ // ```typescript
13
+ // import { sseJsonFrames, fromLangGraphEvents } from "niceeval/adapter";
14
+ //
15
+ // const frames = sseJsonFrames<LangGraphEventLike>(res.body);
16
+ // const stream = fromLangGraphEvents();
17
+ // for (;;) {
18
+ // const frame = await frames.next();
19
+ // if (frame === null) break;
20
+ // events.push(...stream.add(frame)); // 逐帧翻译;认不出的帧返回 []
21
+ // }
22
+ // events.push(...stream.end()); // 放出 seq 缺口后仍压着的乱序帧
23
+ // return { status: stream.status ?? "completed", events, usage: stream.usage };
24
+ // ```
25
+
26
+ import type { InputRequest, JsonValue, StreamEvent, Usage } from "../types.ts";
27
+ import { normalizeToolName } from "../o11y/tool-names.ts";
28
+
29
+ /**
30
+ * `messages` channel 消息里的 content block(LangChain 标准 content blocks 的结构化子集,
31
+ * 只声明转换器要读的字段)。`type: "text"` 读 `text`,`type: "reasoning"` 读 `reasoning`
32
+ * (兼容放在 `text` 里的变体),`type: "tool_call"` 读 `id` / `name` / `args`。
33
+ */
34
+ export interface LangGraphContentBlockLike {
35
+ type: string;
36
+ text?: string;
37
+ reasoning?: string;
38
+ id?: string;
39
+ name?: string;
40
+ args?: unknown;
41
+ [key: string]: unknown;
42
+ }
43
+
44
+ /**
45
+ * LangGraph 事件流协议帧(只声明转换器要读的字段;真实协议帧直接喂进来即可)。
46
+ * `channel` 是事件通道(messages / tools / input / lifecycle,其余通道无对应标准事件),
47
+ * `event` 是通道内事件名,`namespace` 是 subgraph / subagent 层级(自外向内;缺省或空数组
48
+ * = 根图),`seq` 是协议全序号(见 {@link fromLangGraphEvents} 的顺序恢复语义)。
49
+ */
50
+ export interface LangGraphEventLike {
51
+ /** 协议全序号。乱序到达按它恢复顺序;已消费过的 seq(重连补发)按重复丢弃。 */
52
+ seq?: number;
53
+ /** 事件通道:"messages" | "tools" | "input" | "lifecycle";其它通道(values / updates …)忽略。 */
54
+ channel?: string;
55
+ /**
56
+ * 通道内事件名:messages 的 "partial"(增量,忽略)与 "finish"(完整消息 + usage);
57
+ * tools 的 "started" / "finished" / "error";lifecycle 的 "completed" / "failed" / "interrupted"。
58
+ */
59
+ event?: string;
60
+ /** subgraph / subagent 层级(自外向内),如 `["research", "web"]`;段内 `:` 后缀(checkpoint id)不进展示名。 */
61
+ namespace?: readonly string[];
62
+ /** 通道载荷。 */
63
+ data?: Record<string, unknown> | null;
64
+ [key: string]: unknown;
65
+ }
66
+
67
+ /** {@link fromLangGraphEvents} 返回的流句柄。 */
68
+ export interface LangGraphStream {
69
+ /**
70
+ * 逐帧喂协议事件,返回这一帧派生的标准事件。带 seq 且超前于当前水位的帧被暂存,
71
+ * 缺口补齐时按 seq 顺序一起放出;seq 落后于水位的帧视为重连补发的重复帧,丢弃。
72
+ */
73
+ add(event: LangGraphEventLike): StreamEvent[];
74
+ /** 流结束时调用一次:把 seq 缺口后仍暂存的乱序帧按 seq 升序放出(缺帧不再等)。 */
75
+ end(): StreamEvent[];
76
+ /** message finish 逐条累加的用量;协议没报 usage 时是 undefined(不编造数值)。 */
77
+ readonly usage: Usage | undefined;
78
+ /**
79
+ * lifecycle 映射的 Turn 状态:completed / failed 原样,interrupted → "waiting"
80
+ * (HITL 停轮,adapter 据此挂起并等 `input.responses`)。没见过 lifecycle 帧时 undefined。
81
+ */
82
+ readonly status: "completed" | "failed" | "waiting" | undefined;
83
+ /**
84
+ * 拒绝审批后续读前登记:该 tool call 的 tools/error 落成 `status: "rejected"`
85
+ * (人工拒绝)而不是 "failed"(执行故障)。
86
+ */
87
+ markRejected(toolCallId: string): void;
88
+ }
89
+
90
+ function isRecord(v: unknown): v is Record<string, unknown> {
91
+ return typeof v === "object" && v !== null;
92
+ }
93
+
94
+ function str(v: unknown): string | undefined {
95
+ return typeof v === "string" && v ? v : undefined;
96
+ }
97
+
98
+ /** namespace 段的展示名:`node:checkpoint-id` 只留 node 名。 */
99
+ function segmentName(segment: string): string {
100
+ const i = segment.indexOf(":");
101
+ return i > 0 ? segment.slice(0, i) : segment;
102
+ }
103
+
104
+ /**
105
+ * LangGraph 官方事件流(messages / tools / input / lifecycle 四通道)→ 标准事件。
106
+ *
107
+ * - `messages` 的 finish 帧:text block → message、reasoning block → thinking、
108
+ * tool_call block → action.called(与 tools/started 按 call ID 去重),usage 从
109
+ * 消息的 usage_metadata 累加;partial(逐 token 增量)整个忽略。
110
+ * - `tools` 的 started / finished / error → action.called / action.result,按 tool call ID 配对;
111
+ * error 默认 "failed",经 {@link LangGraphStream.markRejected} 登记过的落 "rejected"。
112
+ * - `input` 帧与 lifecycle 的 interrupted → input.requested(同一 interrupt 出现在两处时按
113
+ * id 去重);interrupt 载荷里的 HITL 请求形状(action_request / description / config.allow_*)
114
+ * 映射成 InputRequest 的 action / input / display / options。
115
+ * - `lifecycle`(根 namespace)映射 `status`:completed / failed / interrupted("waiting");
116
+ * failed 帧的错误信息补一条 error 事件。
117
+ * - `namespace` 非空的帧翻译前先为每级未见过的层级补 subagent.called(callId 是自外向内
118
+ * 的路径,层级关系由路径前缀表达);该层级自己的 lifecycle completed / failed 闭合成
119
+ * subagent.completed,根图 completed / failed 时把仍未闭合的层级按同状态一起闭合
120
+ * (interrupted 不闭合——层级还要 resume)。
121
+ * - 帧带 `seq` 时按 seq 恢复协议定义的事件顺序(超前暂存、缺口补齐放出、落后当重复丢弃),
122
+ * 流结束调 {@link LangGraphStream.end} 放出仍压着的乱序帧。
123
+ *
124
+ * 工具名多为应用域内自定义名(canonical 落 "unknown" 即可),只认通用别名基表,
125
+ * 不 opt-in 裸动词别名(同 fromAiSdk 的裁决,见 src/o11y/tool-names.ts)。
126
+ */
127
+ export function fromLangGraphEvents(): LangGraphStream {
128
+ let usage: Usage | undefined;
129
+ let status: "completed" | "failed" | "waiting" | undefined;
130
+
131
+ const startedCallIds = new Set<string>();
132
+ const resolvedCallIds = new Set<string>();
133
+ const rejected = new Set<string>();
134
+ const requestedInputIds = new Set<string>();
135
+ // namespace 路径(join("/"))→ 打开 / 闭合状态。Set 保持插入序,闭合时按深度倒序。
136
+ const openNamespaces = new Set<string>();
137
+ const closedNamespaces = new Set<string>();
138
+
139
+ // seq 顺序恢复:以第一个带 seq 的帧为水位基准,超前的暂存,缺口补齐时连续放出。
140
+ let nextSeq: number | undefined;
141
+ const pendingBySeq = new Map<number, LangGraphEventLike>();
142
+
143
+ let synth = 0;
144
+ const synthCallId = (): string => `lg_${++synth}`;
145
+
146
+ const addUsage = (raw: unknown): void => {
147
+ if (!isRecord(raw)) return;
148
+ const num = (...keys: string[]): number => {
149
+ for (const k of keys) {
150
+ const v = raw[k];
151
+ if (typeof v === "number" && Number.isFinite(v)) return v;
152
+ }
153
+ return 0;
154
+ };
155
+ const input = num("input_tokens", "inputTokens");
156
+ const output = num("output_tokens", "outputTokens");
157
+ if (input === 0 && output === 0) return;
158
+ const details = isRecord(raw.input_token_details) ? raw.input_token_details : undefined;
159
+ const cacheRead = typeof details?.cache_read === "number" ? details.cache_read : 0;
160
+ const cacheWrite = typeof details?.cache_creation === "number" ? details.cache_creation : 0;
161
+ usage = {
162
+ inputTokens: (usage?.inputTokens ?? 0) + input,
163
+ outputTokens: (usage?.outputTokens ?? 0) + output,
164
+ ...(cacheRead || usage?.cacheReadTokens
165
+ ? { cacheReadTokens: (usage?.cacheReadTokens ?? 0) + cacheRead }
166
+ : {}),
167
+ ...(cacheWrite || usage?.cacheWriteTokens
168
+ ? { cacheWriteTokens: (usage?.cacheWriteTokens ?? 0) + cacheWrite }
169
+ : {}),
170
+ requests: (usage?.requests ?? 0) + 1,
171
+ };
172
+ };
173
+
174
+ const emitCalled = (
175
+ events: StreamEvent[],
176
+ callId: string,
177
+ name: string,
178
+ input: unknown,
179
+ ): void => {
180
+ // messages 的 tool_call block 与 tools/started 描述同一次调用,按 call ID 只发一条。
181
+ if (startedCallIds.has(callId)) return;
182
+ startedCallIds.add(callId);
183
+ events.push({
184
+ type: "action.called",
185
+ callId,
186
+ name,
187
+ input: (input ?? null) as JsonValue,
188
+ tool: normalizeToolName(name),
189
+ });
190
+ };
191
+
192
+ const emitResult = (
193
+ events: StreamEvent[],
194
+ callId: string,
195
+ output: unknown,
196
+ status_: "completed" | "failed" | "rejected",
197
+ ): void => {
198
+ if (resolvedCallIds.has(callId)) return;
199
+ resolvedCallIds.add(callId);
200
+ events.push({
201
+ type: "action.result",
202
+ callId,
203
+ ...(output !== undefined ? { output: output as JsonValue } : {}),
204
+ status: status_,
205
+ });
206
+ };
207
+
208
+ /** interrupt / input 载荷 → input.requested(同一 interrupt id 只发一条)。 */
209
+ const emitInputRequested = (events: StreamEvent[], payload: Record<string, unknown>): void => {
210
+ const id = str(payload.id) ?? str(payload.interrupt_id);
211
+ if (id) {
212
+ if (requestedInputIds.has(id)) return;
213
+ requestedInputIds.add(id);
214
+ }
215
+ // interrupt 的载荷在 value 上;input 通道也可能直接给结构化字段。
216
+ const value = "value" in payload ? payload.value : payload;
217
+ const request: {
218
+ id?: string;
219
+ prompt?: string;
220
+ display?: string;
221
+ action?: string;
222
+ input?: JsonValue;
223
+ options?: { id: string; label?: string }[];
224
+ } = {};
225
+ if (id) request.id = id;
226
+ if (typeof value === "string") {
227
+ request.prompt = value;
228
+ } else if (isRecord(value)) {
229
+ // HITL 请求形状(prebuilt HumanInterrupt):action_request / description / config.allow_*。
230
+ const actionRequest = isRecord(value.action_request) ? value.action_request : undefined;
231
+ if (actionRequest) {
232
+ const action = str(actionRequest.action);
233
+ if (action) request.action = action;
234
+ if (actionRequest.args !== undefined) request.input = actionRequest.args as JsonValue;
235
+ }
236
+ const display = str(value.description) ?? str(value.display);
237
+ if (display) request.display = display;
238
+ const prompt = str(value.prompt) ?? str(value.question);
239
+ if (prompt) request.prompt = prompt;
240
+ const config = isRecord(value.config) ? value.config : undefined;
241
+ if (config) {
242
+ const options = (
243
+ [
244
+ ["allow_accept", "accept"],
245
+ ["allow_edit", "edit"],
246
+ ["allow_respond", "respond"],
247
+ ["allow_ignore", "ignore"],
248
+ ] as const
249
+ )
250
+ .filter(([flag]) => config[flag] === true)
251
+ .map(([, optionId]) => ({ id: optionId }));
252
+ if (options.length) request.options = options;
253
+ }
254
+ // 认不出的结构化载荷原样携带,eval 仍可按参数匹配。
255
+ if (
256
+ request.action === undefined &&
257
+ request.display === undefined &&
258
+ request.prompt === undefined &&
259
+ request.input === undefined
260
+ ) {
261
+ request.input = value as JsonValue;
262
+ }
263
+ }
264
+ events.push({ type: "input.requested", request: request as InputRequest });
265
+ };
266
+
267
+ const emitInterrupts = (events: StreamEvent[], data: Record<string, unknown>): void => {
268
+ const interrupts = Array.isArray(data.interrupts)
269
+ ? data.interrupts
270
+ : data.interrupt !== undefined
271
+ ? [data.interrupt]
272
+ : [data];
273
+ for (const item of interrupts) {
274
+ if (isRecord(item)) emitInputRequested(events, item);
275
+ else if (typeof item === "string") emitInputRequested(events, { value: item });
276
+ }
277
+ };
278
+
279
+ /** 为 namespace 的每级未见过的前缀补 subagent.called(层级由路径 callId 表达)。 */
280
+ const ensureNamespace = (events: StreamEvent[], ns: readonly string[]): void => {
281
+ for (let i = 1; i <= ns.length; i++) {
282
+ const key = ns.slice(0, i).join("/");
283
+ if (openNamespaces.has(key) || closedNamespaces.has(key)) continue;
284
+ openNamespaces.add(key);
285
+ events.push({ type: "subagent.called", callId: key, name: segmentName(ns[i - 1]!) });
286
+ }
287
+ };
288
+
289
+ const closeNamespace = (
290
+ events: StreamEvent[],
291
+ key: string,
292
+ status_: "completed" | "failed",
293
+ output?: unknown,
294
+ ): void => {
295
+ if (!openNamespaces.has(key)) return;
296
+ // 先闭合仍打开的更深层级(按深度倒序),再闭合自己——called/completed 嵌套配对。
297
+ const descendants = [...openNamespaces]
298
+ .filter((k) => k.startsWith(`${key}/`))
299
+ .sort((a, b) => b.split("/").length - a.split("/").length);
300
+ for (const child of descendants) {
301
+ openNamespaces.delete(child);
302
+ closedNamespaces.add(child);
303
+ events.push({ type: "subagent.completed", callId: child, status: status_ });
304
+ }
305
+ openNamespaces.delete(key);
306
+ closedNamespaces.add(key);
307
+ events.push({
308
+ type: "subagent.completed",
309
+ callId: key,
310
+ ...(output !== undefined ? { output: output as JsonValue } : {}),
311
+ status: status_,
312
+ });
313
+ };
314
+
315
+ const closeAllOpen = (events: StreamEvent[], status_: "completed" | "failed"): void => {
316
+ const roots = [...openNamespaces].sort((a, b) => b.split("/").length - a.split("/").length);
317
+ for (const key of roots) {
318
+ if (!openNamespaces.has(key)) continue;
319
+ closeNamespace(events, key, status_);
320
+ }
321
+ };
322
+
323
+ const handleMessages = (events: StreamEvent[], frame: LangGraphEventLike): void => {
324
+ // 逐 token 增量帧整个忽略:finish 帧带完整 content blocks(同 fromClaudeSdkMessages
325
+ // 忽略 stream_event 的先例)。
326
+ if (frame.event === "partial" || frame.event === "delta") return;
327
+ const data = isRecord(frame.data) ? frame.data : {};
328
+ const message = isRecord(data.message) ? data.message : data;
329
+ const role = str(message.role) ?? "assistant";
330
+ if (role !== "assistant" && role !== "user") return;
331
+
332
+ const content = message.content;
333
+ if (typeof content === "string") {
334
+ if (content) events.push({ type: "message", role, text: content });
335
+ } else if (Array.isArray(content)) {
336
+ // 按 block 原始顺序落事件,不重排(events.md 不变量 1)。
337
+ for (const block of content) {
338
+ if (!isRecord(block)) continue;
339
+ if (block.type === "text" && typeof block.text === "string" && block.text) {
340
+ events.push({ type: "message", role, text: block.text });
341
+ } else if (block.type === "reasoning") {
342
+ const text = str(block.reasoning) ?? str(block.text);
343
+ if (text) events.push({ type: "thinking", text });
344
+ } else if (block.type === "tool_call" && role === "assistant") {
345
+ const name = str(block.name) ?? "unknown";
346
+ emitCalled(events, str(block.id) ?? synthCallId(), name, block.args);
347
+ }
348
+ }
349
+ }
350
+ // usage 在 message finish 上(usage_metadata;兼容 usage / data.usage 变体)。
351
+ addUsage(message.usage_metadata ?? message.usage ?? data.usage);
352
+ };
353
+
354
+ const handleTools = (events: StreamEvent[], frame: LangGraphEventLike): void => {
355
+ const data = isRecord(frame.data) ? frame.data : {};
356
+ const explicitId = str(data.id) ?? str(data.tool_call_id) ?? str(data.call_id);
357
+ switch (frame.event) {
358
+ case "started": {
359
+ const name = str(data.name) ?? "unknown";
360
+ emitCalled(events, explicitId ?? synthCallId(), name, data.input ?? data.args);
361
+ break;
362
+ }
363
+ case "finished": {
364
+ const callId = explicitId ?? synthCallId();
365
+ emitResult(events, callId, data.output ?? data.result, "completed");
366
+ break;
367
+ }
368
+ case "error": {
369
+ const callId = explicitId ?? synthCallId();
370
+ const message = str(data.error) ?? (isRecord(data.error) ? str(data.error.message) : undefined) ?? str(data.message);
371
+ emitResult(events, callId, message, rejected.has(callId) ? "rejected" : "failed");
372
+ break;
373
+ }
374
+ default:
375
+ break;
376
+ }
377
+ };
378
+
379
+ const handleLifecycle = (
380
+ events: StreamEvent[],
381
+ frame: LangGraphEventLike,
382
+ ns: readonly string[],
383
+ ): void => {
384
+ const data = isRecord(frame.data) ? frame.data : {};
385
+ if (ns.length > 0) {
386
+ // subgraph 生命周期:闭合对应 subagent 层级。interrupted 不闭合(层级还要 resume),
387
+ // 但停轮请求与状态照常上浮——整条 run 一起停。
388
+ if (frame.event === "completed" || frame.event === "failed") {
389
+ closeNamespace(
390
+ events,
391
+ ns.join("/"),
392
+ frame.event,
393
+ data.output ?? data.result,
394
+ );
395
+ } else if (frame.event === "interrupted") {
396
+ status = "waiting";
397
+ emitInterrupts(events, data);
398
+ }
399
+ return;
400
+ }
401
+ switch (frame.event) {
402
+ case "completed": {
403
+ status = "completed";
404
+ closeAllOpen(events, "completed");
405
+ break;
406
+ }
407
+ case "failed": {
408
+ status = "failed";
409
+ const message =
410
+ str(data.error) ??
411
+ (isRecord(data.error) ? str(data.error.message) : undefined) ??
412
+ str(data.message);
413
+ if (message) events.push({ type: "error", message });
414
+ closeAllOpen(events, "failed");
415
+ break;
416
+ }
417
+ case "interrupted": {
418
+ status = "waiting";
419
+ emitInterrupts(events, data);
420
+ break;
421
+ }
422
+ default:
423
+ break;
424
+ }
425
+ };
426
+
427
+ const translate = (frame: LangGraphEventLike): StreamEvent[] => {
428
+ const events: StreamEvent[] = [];
429
+ const ns = Array.isArray(frame.namespace)
430
+ ? frame.namespace.filter((s): s is string => typeof s === "string" && s.length > 0)
431
+ : [];
432
+ ensureNamespace(events, ns);
433
+ switch (frame.channel) {
434
+ case "messages":
435
+ handleMessages(events, frame);
436
+ break;
437
+ case "tools":
438
+ handleTools(events, frame);
439
+ break;
440
+ case "input": {
441
+ const data = isRecord(frame.data) ? frame.data : {};
442
+ emitInputRequested(events, data);
443
+ break;
444
+ }
445
+ case "lifecycle":
446
+ handleLifecycle(events, frame, ns);
447
+ break;
448
+ // values / updates / debug 等其它通道:无对应 StreamEvent。
449
+ default:
450
+ break;
451
+ }
452
+ return events;
453
+ };
454
+
455
+ return {
456
+ get usage() {
457
+ return usage;
458
+ },
459
+ get status() {
460
+ return status;
461
+ },
462
+ markRejected(toolCallId) {
463
+ rejected.add(toolCallId);
464
+ },
465
+ add(frame) {
466
+ const seq = typeof frame.seq === "number" && Number.isFinite(frame.seq) ? frame.seq : undefined;
467
+ if (seq === undefined) return translate(frame);
468
+ if (nextSeq === undefined) nextSeq = seq;
469
+ if (seq < nextSeq) return []; // 重连补发的旧帧:已消费,丢弃
470
+ if (seq > nextSeq) {
471
+ pendingBySeq.set(seq, frame); // 超前:暂存等缺口补齐
472
+ return [];
473
+ }
474
+ const events = translate(frame);
475
+ nextSeq += 1;
476
+ while (pendingBySeq.has(nextSeq)) {
477
+ const next = pendingBySeq.get(nextSeq)!;
478
+ pendingBySeq.delete(nextSeq);
479
+ events.push(...translate(next));
480
+ nextSeq += 1;
481
+ }
482
+ return events;
483
+ },
484
+ end() {
485
+ const events: StreamEvent[] = [];
486
+ const remaining = [...pendingBySeq.entries()].sort(([a], [b]) => a - b);
487
+ pendingBySeq.clear();
488
+ for (const [seq, frame] of remaining) {
489
+ events.push(...translate(frame));
490
+ if (nextSeq === undefined || seq >= nextSeq) nextSeq = seq + 1;
491
+ }
492
+ return events;
493
+ },
494
+ };
495
+ }
@@ -0,0 +1,85 @@
1
+ // marketplace `add` 后的注册名回读校验 —— claude-code / codex 共用的实现(回读命令由各
2
+ // adapter 传入,这里不按 agent 名分支)。为什么必须回读:真实 CLI 的 `marketplace add` 按
3
+ // 目标仓库 manifest 里的 `name` 注册,配置名对不上时 add **静默成功**,直到下一步
4
+ // `plugin install <plugin>@<name>` 才以「找不到 marketplace」的形式间接失败(见
5
+ // memory/native-plugin-marketplace-name-not-caller-assignable.md)。契约:add 之后回读已
6
+ // 注册的 marketplace 列表,配置的 `marketplace.name` 不在其中就立刻抛带两个名字的错误,
7
+ // 不把失败拖延到 install 一步(docs/feature/adapters/architecture/coding-agent-extensions.md)。
8
+
9
+ import { t } from "../i18n/index.ts";
10
+ import type { Sandbox } from "../types.ts";
11
+
12
+ /**
13
+ * `<cli> plugin marketplace list --json` 的输出里抠注册名。形状按 CLI 宽容解析:裸数组
14
+ * (元素是字符串,或带 name / marketplaceName / id 的对象)或 `{ marketplaces: [...] }` 容器;
15
+ * 抠不出(非 JSON / 未知形状)返回 undefined —— 调用方按「回读失败」处理,不静默放行
16
+ * (形状硬猜的教训见 memory/codex-plugin-list-json-shape-guessed-wrong.md)。
17
+ */
18
+ export function marketplaceNamesFromList(stdout: string): string[] | undefined {
19
+ let raw: unknown;
20
+ try {
21
+ raw = JSON.parse(stdout);
22
+ } catch {
23
+ return undefined;
24
+ }
25
+ const arr = Array.isArray(raw)
26
+ ? raw
27
+ : raw && typeof raw === "object"
28
+ ? (raw as { marketplaces?: unknown }).marketplaces
29
+ : undefined;
30
+ if (!Array.isArray(arr)) return undefined;
31
+ const names: string[] = [];
32
+ for (const item of arr) {
33
+ const name =
34
+ typeof item === "string"
35
+ ? item
36
+ : item && typeof item === "object"
37
+ ? ((item as { name?: unknown }).name ??
38
+ (item as { marketplaceName?: unknown }).marketplaceName ??
39
+ (item as { id?: unknown }).id)
40
+ : undefined;
41
+ if (typeof name === "string" && name) names.push(name);
42
+ }
43
+ return names;
44
+ }
45
+
46
+ export interface VerifyMarketplaceNameOptions {
47
+ /** 报错归属的 agent 名(如 "claude-code")。 */
48
+ agent: string;
49
+ /** 回读命令(如 `claude plugin marketplace list --json`)。 */
50
+ listCommand: string;
51
+ /** 配置里声明的 marketplace(name 必须等于目标仓库 manifest 声明的 name)。 */
52
+ marketplace: { name: string; source: string };
53
+ /** 本次 setup 已校验过的 marketplace 名 —— 从「实际注册了什么」里剔除,让报错聚焦这次 add 新注册的名字。 */
54
+ knownNames: ReadonlySet<string>;
55
+ }
56
+
57
+ /**
58
+ * `marketplace add` 成功后回读注册列表,校验配置的 `marketplace.name` 真的被注册。
59
+ * 回读失败(命令非零退出 / 输出解析不出)与名字不匹配都立刻抛错,attempt 在 setup 阶段
60
+ * errored —— 不把失败拖延到 `plugin install`。
61
+ */
62
+ export async function verifyMarketplaceName(sb: Sandbox, opts: VerifyMarketplaceNameOptions): Promise<void> {
63
+ const res = await sb.runShell(opts.listCommand);
64
+ const names = res.exitCode === 0 ? marketplaceNamesFromList(res.stdout) : undefined;
65
+ if (names === undefined) {
66
+ throw new Error(
67
+ t("plugin.marketplaceVerifyFailed", {
68
+ agent: opts.agent,
69
+ name: opts.marketplace.name,
70
+ command: opts.listCommand,
71
+ tail: (res.stdout + res.stderr).trim().split("\n").slice(-12).join("\n"),
72
+ }),
73
+ );
74
+ }
75
+ if (names.includes(opts.marketplace.name)) return;
76
+ const actual = names.filter((n) => !opts.knownNames.has(n));
77
+ throw new Error(
78
+ t("plugin.marketplaceNameMismatch", {
79
+ agent: opts.agent,
80
+ expected: opts.marketplace.name,
81
+ source: opts.marketplace.source,
82
+ actual: actual.length ? actual.join(", ") : "(none)",
83
+ }),
84
+ );
85
+ }