@hona/openeval 0.2.2 → 0.3.0

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 (211) hide show
  1. package/JUDGING.md +134 -23
  2. package/README.md +67 -9
  3. package/package.json +3 -2
  4. package/src/app/cost-plan.ts +20 -16
  5. package/src/app/eval-state.ts +2 -1
  6. package/src/app/grade-recording.ts +72 -0
  7. package/src/app/input-fingerprints.ts +22 -8
  8. package/src/app/judge-evidence.ts +26 -11
  9. package/src/app/judge-run.ts +36 -19
  10. package/src/app/load-benchmark.ts +46 -15
  11. package/src/app/read-results.ts +35 -2
  12. package/src/app/read-run.ts +23 -2
  13. package/src/app/rejudge.ts +2 -1
  14. package/src/app/run-benchmark.ts +4 -0
  15. package/src/app/run-eval-pipeline.ts +2 -1
  16. package/src/app/run-eval.ts +13 -0
  17. package/src/app/scores.ts +76 -34
  18. package/src/app/serve-results.ts +2 -0
  19. package/src/evidence.ts +4 -1
  20. package/src/index.ts +15 -3
  21. package/src/infra/evidence/index.ts +39 -2
  22. package/src/infra/evidence/tool-calls.ts +5 -2
  23. package/src/infra/judging/agent.ts +3 -2
  24. package/src/infra/judging/code-result.ts +102 -0
  25. package/src/infra/judging/code-source.ts +96 -0
  26. package/src/infra/judging/code-worker.ts +26 -0
  27. package/src/infra/judging/code.ts +97 -0
  28. package/src/infra/judging/contract.ts +82 -64
  29. package/src/infra/judging/evidence-tool.ts +3 -1
  30. package/src/infra/judging/index.ts +14 -0
  31. package/src/infra/judging/judge-agent.md +24 -18
  32. package/src/infra/judging/observer.ts +8 -7
  33. package/src/infra/judging/submission.ts +8 -8
  34. package/src/infra/judging/tools.ts +9 -9
  35. package/src/infra/opencode/host.ts +1 -1
  36. package/src/infra/opencode/read-recording.ts +6 -1
  37. package/src/infra/opencode/version.ts +1 -0
  38. package/src/infra/recording/index.ts +244 -0
  39. package/src/infra/recording/metrics.ts +193 -0
  40. package/src/infra/sqlite/index.ts +29 -19
  41. package/src/judge-context.ts +141 -0
  42. package/src/judgment.ts +26 -17
  43. package/src/types.ts +36 -17
  44. package/src/view.ts +40 -14
  45. package/viewer/assets/{abnfDiagram-VCTEODGH-B1kcYgQG.js → abnfDiagram-VCTEODGH-rgmalTBh.js} +1 -1
  46. package/viewer/assets/{angular-html-DrYCUiZv.js → angular-html-CcaajvLj.js} +1 -1
  47. package/viewer/assets/{angular-ts-BgjwQn-Z.js → angular-ts-COx6AtG2.js} +1 -1
  48. package/viewer/assets/{apl-DlHqe8o4.js → apl-lBEnC_vz.js} +1 -1
  49. package/viewer/assets/{arc-C4nf7ZoY.js → arc-BQ0cQOwK.js} +1 -1
  50. package/viewer/assets/architecture-7GRP2DOG-DcUKSnYa.js +1 -0
  51. package/viewer/assets/{architectureDiagram-5GKGNRK7-BIWoe0fO.js → architectureDiagram-5GKGNRK7-_XIJP-LV.js} +1 -1
  52. package/viewer/assets/{astro-BrlHnIh9.js → astro-Ca8FrXna.js} +1 -1
  53. package/viewer/assets/{blade-DOcerOiW.js → blade-B07ZVjUX.js} +1 -1
  54. package/viewer/assets/{blockDiagram-I7D4REHJ-C3x13a9m.js → blockDiagram-I7D4REHJ-BdcDRYB-.js} +1 -1
  55. package/viewer/assets/{c-BMbThxEo.js → c-4MsqXzJK.js} +1 -1
  56. package/viewer/assets/{c4Diagram-7LVT6UL2-BeWktchx.js → c4Diagram-7LVT6UL2-E3nvwqBd.js} +1 -1
  57. package/viewer/assets/channel-CViXUg9J.js +1 -0
  58. package/viewer/assets/{chapel-k2cjkSmc.js → chapel-BlwR_8zu.js} +1 -1
  59. package/viewer/assets/{chunk-4HAMMTFA-o6YtJsqE.js → chunk-4HAMMTFA-DN_vtpqx.js} +1 -1
  60. package/viewer/assets/{chunk-75Z2AOVW-KohQa9Nw.js → chunk-75Z2AOVW-DWMGmicp.js} +1 -1
  61. package/viewer/assets/{chunk-DU6HZSFF-C88wxc6m.js → chunk-DU6HZSFF-D2RGHn75.js} +1 -1
  62. package/viewer/assets/{chunk-F27PBJKO-mO4AP2as.js → chunk-F27PBJKO-wfQZ5Ilx.js} +1 -1
  63. package/viewer/assets/{chunk-GMAD6QVW-DzQX2Cld.js → chunk-GMAD6QVW-DrlfUg7k.js} +1 -1
  64. package/viewer/assets/{chunk-GVQU2GXP-CS78RfJD.js → chunk-GVQU2GXP-BHoRLKIU.js} +1 -1
  65. package/viewer/assets/{chunk-IMKFNOWR-DxS42GUo.js → chunk-IMKFNOWR-mxLyQhYU.js} +1 -1
  66. package/viewer/assets/{chunk-L3NEJ4N5-DGo-uYLy.js → chunk-L3NEJ4N5-IpGl-pvp.js} +1 -1
  67. package/viewer/assets/{chunk-OSK3NFVY-BOcXQG16.js → chunk-OSK3NFVY-t0yN50X6.js} +1 -1
  68. package/viewer/assets/{chunk-P2QGCYS3-B7GnZdIQ.js → chunk-P2QGCYS3-BPBYYOcK.js} +1 -1
  69. package/viewer/assets/{chunk-POPQ4Y6H-DgBcYHog.js → chunk-POPQ4Y6H-DIOcv86B.js} +1 -1
  70. package/viewer/assets/{chunk-PWAF6VOD-DpEq6qHA.js → chunk-PWAF6VOD-CqVB-mzq.js} +1 -1
  71. package/viewer/assets/{chunk-SHT3W25Y-CrOGxKM2.js → chunk-SHT3W25Y-DwOEgFIr.js} +1 -1
  72. package/viewer/assets/{chunk-SVP7TREG-4HY7Fljj.js → chunk-SVP7TREG-Dfg1jHc3.js} +1 -1
  73. package/viewer/assets/{chunk-TICWLB2K-B1Rl31EC.js → chunk-TICWLB2K-DE6tQNRu.js} +1 -1
  74. package/viewer/assets/{chunk-XXDRQBXY-CK8giEW-.js → chunk-XXDRQBXY-Bu8dW06M.js} +1 -1
  75. package/viewer/assets/classDiagram-ZZMXUADV-O1Zm0_p8.js +1 -0
  76. package/viewer/assets/classDiagram-v2-VYDZK3BY-O1Zm0_p8.js +1 -0
  77. package/viewer/assets/{cobol-CupUwvw9.js → cobol-BS_DrT-b.js} +1 -1
  78. package/viewer/assets/{coffee-C17pkozF.js → coffee-Clk97ecB.js} +1 -1
  79. package/viewer/assets/{cose-bilkent-JH36ORCC-V1OmrmCv.js → cose-bilkent-JH36ORCC-B3xA8n2e.js} +1 -1
  80. package/viewer/assets/{cpp-BJwVQVXg.js → cpp-aunMdKLY.js} +1 -1
  81. package/viewer/assets/{crystal-BF-oCN-L.js → crystal-B1oihgG5.js} +1 -1
  82. package/viewer/assets/{css-BMgkVI3c.js → css-ClOubSVo.js} +1 -1
  83. package/viewer/assets/{cynefin-OW5HDTMX-BEjY_gsc.js → cynefin-OW5HDTMX-DdEeq9jN.js} +1 -1
  84. package/viewer/assets/{cynefinDiagram-5FMLGOSQ-bfSYgr6z.js → cynefinDiagram-5FMLGOSQ-BwHCV_5V.js} +1 -1
  85. package/viewer/assets/{dagre-GXQ25YYZ-VtwTgGfa.js → dagre-GXQ25YYZ-BkuWRmq-.js} +1 -1
  86. package/viewer/assets/{diagram-S7CK7UJ4-CHaAAyW7.js → diagram-S7CK7UJ4-C5WGyCny.js} +1 -1
  87. package/viewer/assets/{diagram-UQ7AKVKN-CjL65-As.js → diagram-UQ7AKVKN-mdununKH.js} +1 -1
  88. package/viewer/assets/{diagram-VSXAHHWV-Cnxrk1TJ.js → diagram-VSXAHHWV-CpuvU9Ij.js} +1 -1
  89. package/viewer/assets/{diagram-VX7I27RA-D78jPXr7.js → diagram-VX7I27RA-CcLmTPwv.js} +1 -1
  90. package/viewer/assets/{diagram-Z3DM3KII-DKjnOaF2.js → diagram-Z3DM3KII-DWCnKyKY.js} +1 -1
  91. package/viewer/assets/{dist-BTYPW0dz.js → dist-1DE7d697.js} +1 -1
  92. package/viewer/assets/{ebnfDiagram-PWID7BFC-7XiXh5Lx.js → ebnfDiagram-PWID7BFC-9M_-_M-B.js} +1 -1
  93. package/viewer/assets/{edge-B34N_HjN.js → edge-NsPpWeFX.js} +1 -1
  94. package/viewer/assets/{elixir-CQzWMyKD.js → elixir-CXZ4zQTn.js} +1 -1
  95. package/viewer/assets/{elm-BNJiG-wg.js → elm-DVXugwZs.js} +1 -1
  96. package/viewer/assets/{erDiagram-RLTQ6QDP-YPFEgZxU.js → erDiagram-RLTQ6QDP-B2j6iCkZ.js} +1 -1
  97. package/viewer/assets/{erb-D5Rm3UNa.js → erb-h8ybsnSQ.js} +1 -1
  98. package/viewer/assets/eventmodeling-NTZA5JFV-CpMf6avu.js +1 -0
  99. package/viewer/assets/flowDiagram-HODETNUW-DIKEcF1k.js +1 -0
  100. package/viewer/assets/{ganttDiagram-EL5Y4UJY-ergSGtTP.js → ganttDiagram-EL5Y4UJY-CR2QLJ-I.js} +1 -1
  101. package/viewer/assets/{git-rebase-BpIMZIRG.js → git-rebase-CT9f4kn8.js} +1 -1
  102. package/viewer/assets/{gitGraph-4MIJSDKK-CZt4REVj.js → gitGraph-4MIJSDKK-DGw-0dqe.js} +1 -1
  103. package/viewer/assets/{gitGraphDiagram-WWUBYQGX-h9QMzC2r.js → gitGraphDiagram-WWUBYQGX-BW112jVV.js} +1 -1
  104. package/viewer/assets/{glimmer-js-SeSwns5_.js → glimmer-js-B17pi1gL.js} +1 -1
  105. package/viewer/assets/{glimmer-ts-BExI5tTz.js → glimmer-ts-CNmwRbOn.js} +1 -1
  106. package/viewer/assets/{glsl-CXikKw2b.js → glsl-By4kbzYa.js} +1 -1
  107. package/viewer/assets/{graphql-6GpQIInt.js → graphql-BrDJs0bG.js} +1 -1
  108. package/viewer/assets/{hack-i7rizvpB.js → hack-NosEIqRi.js} +1 -1
  109. package/viewer/assets/{haml-C68gmkTf.js → haml-B-GQD8Zj.js} +1 -1
  110. package/viewer/assets/{handlebars-BBn_c0Xt.js → handlebars-1vSTccT5.js} +1 -1
  111. package/viewer/assets/{html-BObmAZQA.js → html-BBEodwWE.js} +1 -1
  112. package/viewer/assets/{html-derivative-BjMuR1LM.js → html-derivative-ClDEAvlc.js} +1 -1
  113. package/viewer/assets/{http-BJBk8gt0.js → http-I0uH89QQ.js} +1 -1
  114. package/viewer/assets/{hurl-D7obCYfg.js → hurl-JU97iWqA.js} +1 -1
  115. package/viewer/assets/{index-FMtYXcX7.js → index-CXBRQlCH.js} +5 -5
  116. package/viewer/assets/{index-Wz0qQyIk.css → index-D6HZB4Yz.css} +1 -1
  117. package/viewer/assets/{info-A6RAGUB7-CrzitQIU.js → info-A6RAGUB7-aRrZ0_i2.js} +1 -1
  118. package/viewer/assets/{infoDiagram-27XIBGKW-D2CgRDxu.js → infoDiagram-27XIBGKW-BnD3mUdL.js} +1 -1
  119. package/viewer/assets/{ishikawaDiagram-5VMMS53U-BhnQMHOo.js → ishikawaDiagram-5VMMS53U-u-wcafFg.js} +1 -1
  120. package/viewer/assets/{java-5VJU_EW8.js → java-BxAxXJLN.js} +1 -1
  121. package/viewer/assets/{javascript-r8UgndxQ.js → javascript-DNepMMwm.js} +1 -1
  122. package/viewer/assets/{jinja-BY2ibHG5.js → jinja-CfKdKg9T.js} +1 -1
  123. package/viewer/assets/{jison-C_7YxymZ.js → jison-C62ElGA9.js} +1 -1
  124. package/viewer/assets/{journeyDiagram-3NMN7TZE-DSBzKQEa.js → journeyDiagram-3NMN7TZE-E6uhr_fk.js} +1 -1
  125. package/viewer/assets/{json-8e1WlJYD.js → json-DSQqeQzH.js} +1 -1
  126. package/viewer/assets/{jsx-BYp5GD4Y.js → jsx-CpZi9Ceb.js} +1 -1
  127. package/viewer/assets/{julia-Bkob1uVX.js → julia-CypyJgFE.js} +1 -1
  128. package/viewer/assets/{just-CUnSjty-.js → just-_p8XAy70.js} +1 -1
  129. package/viewer/assets/{kanban-definition-UXKFOSKX-CotbrQue.js → kanban-definition-UXKFOSKX-Blo6hDj7.js} +1 -1
  130. package/viewer/assets/{latex-UdE3ivqk.js → latex-Blby6_jP.js} +1 -1
  131. package/viewer/assets/{line-DDh6a6hz.js → line-CQc5Trjj.js} +1 -1
  132. package/viewer/assets/{linear-BxMsCbKb.js → linear-BjWjnaEC.js} +1 -1
  133. package/viewer/assets/{liquid-WL4MvEIT.js → liquid-x3m9Bx2x.js} +1 -1
  134. package/viewer/assets/{lua-CtOubZta.js → lua-Da9XAgwV.js} +1 -1
  135. package/viewer/assets/{marko-C0GSQJi3.js → marko-Gu1d_h3t.js} +1 -1
  136. package/viewer/assets/{mdc-DUivddFc.js → mdc-CM0pUbEe.js} +1 -1
  137. package/viewer/assets/{mermaid-parser.core-FhKE_uv2.js → mermaid-parser.core-DR191xUW.js} +3 -3
  138. package/viewer/assets/{mermaid.core-BSOWwyvd.js → mermaid.core-DnwMH2U1.js} +4 -4
  139. package/viewer/assets/{mindmap-definition-YA3MSWOX-tsLZ_Opn.js → mindmap-definition-YA3MSWOX-KrnPcZeq.js} +1 -1
  140. package/viewer/assets/{nginx-B__INAtC.js → nginx-DAKCD0HY.js} +1 -1
  141. package/viewer/assets/{nim-UEY12GJF.js → nim-BvNTuhqF.js} +1 -1
  142. package/viewer/assets/{org-DDIZsz95.js → org-B-tOc6Cc.js} +1 -1
  143. package/viewer/assets/{packet-AYTQ26CC-CqdJnqfo.js → packet-AYTQ26CC-Cl2KBEou.js} +1 -1
  144. package/viewer/assets/{pegDiagram-XKGWAZYB-BJs7qu7m.js → pegDiagram-XKGWAZYB--GvmNzTl.js} +1 -1
  145. package/viewer/assets/{perl-D5KwNZt7.js → perl-RyNpCOE-.js} +1 -1
  146. package/viewer/assets/{php-BEs7d_Dk.js → php-D5gke6Nh.js} +1 -1
  147. package/viewer/assets/{pie-WAS4IAKB-lgzqBWqy.js → pie-WAS4IAKB-RfZPDbVI.js} +1 -1
  148. package/viewer/assets/{pieDiagram-E7YTZNPT-Cxe9LnnE.js → pieDiagram-E7YTZNPT-BbcD9IEY.js} +1 -1
  149. package/viewer/assets/{pug-B0dsfQOV.js → pug-CSZdPWzF.js} +1 -1
  150. package/viewer/assets/{qml-XPIhrIzf.js → qml-CCUSNo18.js} +1 -1
  151. package/viewer/assets/{quadrantDiagram-AXDQQJYC-D5JGwC11.js → quadrantDiagram-AXDQQJYC-DHP-nbSh.js} +1 -1
  152. package/viewer/assets/{r-C6hQdpIM.js → r-Cl6F5Lf8.js} +1 -1
  153. package/viewer/assets/{radar-RG4KPBEZ-B7JRLhUA.js → radar-RG4KPBEZ-DdfC1yOQ.js} +1 -1
  154. package/viewer/assets/{railroad-74A4TZTK-Df7ZQBLn.js → railroad-74A4TZTK-MHxcKgVS.js} +1 -1
  155. package/viewer/assets/railroad-abnf-HS5TGJTU-yEJXYTCu.js +1 -0
  156. package/viewer/assets/railroad-ebnf-LZEXJU2U-CEc058YK.js +1 -0
  157. package/viewer/assets/railroad-peg-WCYAUIDC-0iFSHGbV.js +1 -0
  158. package/viewer/assets/{railroadDiagram-O6MQD6OU-DZJzpmP8.js → railroadDiagram-O6MQD6OU-sxdxPqy-.js} +1 -1
  159. package/viewer/assets/{razor-C3KSUuOw.js → razor-hm0vLE-h.js} +1 -1
  160. package/viewer/assets/{regexp-n-T936Rk.js → regexp-BhIot3VY.js} +1 -1
  161. package/viewer/assets/{requirementDiagram-BXWQKSXE-Be9lEFrk.js → requirementDiagram-BXWQKSXE-CrZXt7Yu.js} +1 -1
  162. package/viewer/assets/{rst-fVnHDyXD.js → rst-C3giCu1C.js} +1 -1
  163. package/viewer/assets/{ruby-DuRgxlXF.js → ruby-B6fOYc5V.js} +1 -1
  164. package/viewer/assets/{sankeyDiagram-P5KCCOFB-BsgKAEBN.js → sankeyDiagram-P5KCCOFB-BVK_LjzN.js} +1 -1
  165. package/viewer/assets/{sas-C0WA2OFa.js → sas-a9NUQlsy.js} +1 -1
  166. package/viewer/assets/{scss-CQDZ4smR.js → scss-ChVMSp7G.js} +1 -1
  167. package/viewer/assets/{sequenceDiagram-WJ2MYXX4-BkUDWL0D.js → sequenceDiagram-WJ2MYXX4-B4E1wY9a.js} +1 -1
  168. package/viewer/assets/{shellscript-CE84G0GP.js → shellscript-Dxbnxou2.js} +1 -1
  169. package/viewer/assets/{shellsession-BdJvdaqL.js → shellsession-DWcRFAj_.js} +1 -1
  170. package/viewer/assets/{soy-BSxOREC0.js → soy-C-D-pvKx.js} +1 -1
  171. package/viewer/assets/{sql-D4MN9uRI.js → sql-W7nl9Rxy.js} +1 -1
  172. package/viewer/assets/{src-BT_kESTA.js → src-C_D1vmKd.js} +1 -1
  173. package/viewer/assets/{stata-B6nXAaGV.js → stata-BWq3CAeQ.js} +1 -1
  174. package/viewer/assets/{stateDiagram-D77RDMKH-C80wp8xG.js → stateDiagram-D77RDMKH-Dc8nXUDp.js} +1 -1
  175. package/viewer/assets/stateDiagram-v2-MP3YSRHH-SMxxOURe.js +1 -0
  176. package/viewer/assets/{surrealql-fVjZmhV3.js → surrealql-BkXUcx4E.js} +1 -1
  177. package/viewer/assets/{svelte-DnBQ5p7d.js → svelte-CowGUt9l.js} +1 -1
  178. package/viewer/assets/{swimlanes-42K2YHIH-Cff6yT8e.js → swimlanes-42K2YHIH-BtZAMLQ7.js} +1 -1
  179. package/viewer/assets/swimlanesDiagram-VR7AAH4N-C_XphOba.js +8 -0
  180. package/viewer/assets/{templ-TCx51Uts.js → templ-ejLYfFxc.js} +1 -1
  181. package/viewer/assets/{tex-CF6SMAQ_.js → tex-McefVK6r.js} +1 -1
  182. package/viewer/assets/{timeline-definition-24CTP7MA-Cje7BvaN.js → timeline-definition-24CTP7MA-TvSh-paH.js} +1 -1
  183. package/viewer/assets/{treeView-Q6P3EWNA-D-QLY3IO.js → treeView-Q6P3EWNA-BRIxIFdd.js} +1 -1
  184. package/viewer/assets/{treemap-WGGIJYW6-BCHcTa4N.js → treemap-WGGIJYW6-CeY__ApH.js} +1 -1
  185. package/viewer/assets/{ts-tags-BqLRnADX.js → ts-tags-D8LHJIWj.js} +1 -1
  186. package/viewer/assets/{tsx-D7KpDX0b.js → tsx-BkPshBHh.js} +1 -1
  187. package/viewer/assets/{twig-CEZLObpN.js → twig-CmfWCxbk.js} +1 -1
  188. package/viewer/assets/{typescript-DAbIDLbf.js → typescript-DnVmzADb.js} +1 -1
  189. package/viewer/assets/{typst-CqN7-whv.js → typst-BpEOIbiv.js} +1 -1
  190. package/viewer/assets/{vennDiagram-4TSXK5OY-Bz20rx02.js → vennDiagram-4TSXK5OY-Ba-jimVX.js} +1 -1
  191. package/viewer/assets/{vue-html-DQmvvKbd.js → vue-html-C-XYg42q.js} +1 -1
  192. package/viewer/assets/{vue-vine-Did3qTVW.js → vue-vine-DK68dTen.js} +1 -1
  193. package/viewer/assets/{vue-kTxN6wdc.js → vue-zII4ln6o.js} +1 -1
  194. package/viewer/assets/{wardley-WFR3VGLG-CpXaqOit.js → wardley-WFR3VGLG-e2A2dYzM.js} +1 -1
  195. package/viewer/assets/{wardleyDiagram-VM6X3IG4-Cz06jHpA.js → wardleyDiagram-VM6X3IG4-D2Rq1FsU.js} +1 -1
  196. package/viewer/assets/{xml-te2hFcAA.js → xml-Cc-dIbaq.js} +1 -1
  197. package/viewer/assets/{xsl-X4fuKSVl.js → xsl-C7A6UsbO.js} +1 -1
  198. package/viewer/assets/{xychartDiagram-S5SC5T6Z-BXCw0sfu.js → xychartDiagram-S5SC5T6Z-De55Qsdg.js} +1 -1
  199. package/viewer/assets/{yaml-NHtDSZab.js → yaml-BJLNLIRM.js} +1 -1
  200. package/viewer/index.html +2 -2
  201. package/viewer/assets/architecture-7GRP2DOG-Be250VKZ.js +0 -1
  202. package/viewer/assets/channel-DtULXRee.js +0 -1
  203. package/viewer/assets/classDiagram-ZZMXUADV-Bmqu01Xf.js +0 -1
  204. package/viewer/assets/classDiagram-v2-VYDZK3BY-Bmqu01Xf.js +0 -1
  205. package/viewer/assets/eventmodeling-NTZA5JFV-CJCg5f6p.js +0 -1
  206. package/viewer/assets/flowDiagram-HODETNUW-BFzq4Zvs.js +0 -1
  207. package/viewer/assets/railroad-abnf-HS5TGJTU-Z8q3QVTf.js +0 -1
  208. package/viewer/assets/railroad-ebnf-LZEXJU2U-BWoRkG3W.js +0 -1
  209. package/viewer/assets/railroad-peg-WCYAUIDC-BzoV1cN7.js +0 -1
  210. package/viewer/assets/stateDiagram-v2-MP3YSRHH-CYdsKKlz.js +0 -1
  211. package/viewer/assets/swimlanesDiagram-VR7AAH4N-niTUGjMT.js +0 -8
@@ -11,6 +11,7 @@ import type { EvidenceFeed, EvidenceView, EvidenceQuery } from "../../evidence";
11
11
  export type { EvidenceQuery } from "../../evidence";
12
12
  import { trackTool } from "./tool-calls";
13
13
  import { hash as sha256, writeJson } from "../files";
14
+ import { measureRecording } from "../recording/metrics";
14
15
  type InferenceResponse = { text: string; textBlocks?: string[] };
15
16
  type ArtifactPolicy = { excludeDirectories: string[] };
16
17
 
@@ -25,7 +26,7 @@ export type EvidenceQueryAudit = {
25
26
  checkId?: string;
26
27
  checkpoint?: EvidenceCheckpoint;
27
28
  };
28
- type FileEntry = {
29
+ export type FileEntry = {
29
30
  path: string;
30
31
  bytes: number;
31
32
  sha256?: string;
@@ -37,7 +38,7 @@ export type EvidenceEvent = {
37
38
  time: string;
38
39
  event: OpenCodeStreamEvent;
39
40
  };
40
- type EvidenceManifest = {
41
+ export type EvidenceManifest = {
41
42
  version: 1;
42
43
  handle: "candidate";
43
44
  coverage: "recorded-session" | "recorded-prefix";
@@ -446,6 +447,28 @@ export class CandidateEvidence {
446
447
  readonly hash: string,
447
448
  ) {}
448
449
 
450
+ /** Native records for trusted code judges. LLM queries use the separate blinded view. */
451
+ rawEvents(): EvidenceEvent[] {
452
+ return structuredClone(this.records);
453
+ }
454
+
455
+ toolCalls(): ToolCall[] {
456
+ return structuredClone(this.manifest.tools);
457
+ }
458
+
459
+ files(revision: "initial" | "final" = "final"): FileEntry[] {
460
+ const snapshot = this.manifest[revision];
461
+ if (!snapshot)
462
+ throw new Error(`${revision} workspace snapshot is unavailable`);
463
+ return structuredClone(snapshot.files);
464
+ }
465
+
466
+ async readFile(path: string, revision: "initial" | "final" = "final") {
467
+ const entry = this.entry(path, revision);
468
+ if (!entry) throw new Error(`Artifact not found: ${path}`);
469
+ return this.bytes(entry);
470
+ }
471
+
449
472
  view(reference: EvidenceRef): EvidenceView {
450
473
  if (reference.hash !== this.hash)
451
474
  throw new Error("Evidence view reference does not match its archive");
@@ -629,6 +652,20 @@ export class CandidateEvidence {
629
652
 
630
653
  async query(input: EvidenceQuery): Promise<Record<string, unknown>> {
631
654
  if (input.action === "summary") return this.summary();
655
+ if (input.action === "metrics") {
656
+ const metrics = measureRecording(this.records, this.manifest.tools, {
657
+ directory: this.directory,
658
+ hash: this.hash,
659
+ });
660
+ if (!input.metric) return { ...metrics };
661
+ let value: unknown = metrics;
662
+ for (const key of input.metric.split(".")) {
663
+ if (!value || typeof value !== "object" || !Object.hasOwn(value, key))
664
+ throw new Error(`Unknown recorded metric: ${input.metric}`);
665
+ value = (value as Record<string, unknown>)[key];
666
+ }
667
+ return { metric: input.metric, value, evidence: metrics.evidence };
668
+ }
632
669
  if (input.action === "response")
633
670
  return textPage(this.manifest.response.text, input);
634
671
  if (input.action === "events")
@@ -8,6 +8,7 @@ export function trackTool(
8
8
  if (!("data" in event)) return;
9
9
  const data = event.data as {
10
10
  id?: string;
11
+ sessionID?: string;
11
12
  name?: string;
12
13
  assistantMessageID?: string;
13
14
  input?: Record<string, unknown>;
@@ -17,8 +18,10 @@ export function trackTool(
17
18
  executed?: boolean;
18
19
  };
19
20
  if (!data.id) return;
20
- const value = calls.get(data.id) ?? {
21
+ const key = `${data.sessionID ?? ""}:${data.assistantMessageID ?? ""}:${data.id}`;
22
+ const value = calls.get(key) ?? {
21
23
  id: data.id,
24
+ sessionID: data.sessionID,
22
25
  name: data.name ?? "unknown",
23
26
  assistantMessageId: data.assistantMessageID ?? "",
24
27
  status: "preparing" as const,
@@ -45,5 +48,5 @@ export function trackTool(
45
48
  if (value.startedAt !== undefined)
46
49
  value.durationMs = event.created - value.startedAt;
47
50
  }
48
- calls.set(value.id, value);
51
+ calls.set(key, value);
49
52
  }
@@ -18,7 +18,7 @@ export const JUDGE_TOOLS = [
18
18
  */
19
19
  export const JUDGE_AGENT = {
20
20
  id: "openeval-judge",
21
- description: "Grades immutable recorded work against eval metrics",
21
+ description: "Grades immutable recorded work against eval criteria",
22
22
  mode: "primary",
23
23
  system: readFileSync(new URL("./judge-agent.md", import.meta.url), "utf8"),
24
24
  permissions: [
@@ -35,6 +35,7 @@ export async function judgeConfiguration(
35
35
  input: Pick<JudgeRunInput, "agent" | "rubric" | "websearch">,
36
36
  directory: string,
37
37
  ) {
38
+ if (!input.agent) throw new Error("An LLM judge requires an agent profile");
38
39
  const { id, ...agent } = input.agent;
39
40
  const configuration = resolve(directory, "configuration");
40
41
  const config = {
@@ -45,7 +46,7 @@ export async function judgeConfiguration(
45
46
  ...agent,
46
47
  // Keep the rubric in the native system prompt so compaction cannot lose
47
48
  // it. Request context and result data move through registered tools.
48
- system: `${agent.system.trim()}\n\n# Eval metrics\n\n${input.rubric.trim()}`,
49
+ system: `${agent.system.trim()}\n\n# Eval rubric\n\n${input.rubric.trim()}`,
49
50
  },
50
51
  },
51
52
  websearch: input.websearch ? { provider: input.websearch } : false,
@@ -0,0 +1,102 @@
1
+ import type { JsonValue } from "../../judge-context";
2
+ import type { CriterionScore, Judgment } from "../../types";
3
+ import { criterionMean, isScored } from "../../judgment";
4
+
5
+ /** Preserve author JSON exactly; reject values JSON would silently discard or coerce. */
6
+ export function jsonOutput(
7
+ value: unknown,
8
+ path = "output",
9
+ seen = new Set<object>(),
10
+ ): JsonValue {
11
+ if (value === null || typeof value === "boolean" || typeof value === "string")
12
+ return value;
13
+ if (typeof value === "number" && Number.isFinite(value)) return value;
14
+ if (typeof value !== "object" || !value || seen.has(value))
15
+ throw new Error(
16
+ `${path} must be JSON-compatible (no undefined, non-finite numbers, or cycles)`,
17
+ );
18
+ if (
19
+ !Array.isArray(value) &&
20
+ ![Object.prototype, null].includes(Object.getPrototypeOf(value))
21
+ )
22
+ throw new Error(`${path} must be a plain JSON object`);
23
+ if (Object.getOwnPropertySymbols(value).length)
24
+ throw new Error(`${path} contains a symbol key`);
25
+ seen.add(value);
26
+ try {
27
+ if (Array.isArray(value))
28
+ return Array.from(value, (item, index) =>
29
+ jsonOutput(item, `${path}[${index}]`, seen),
30
+ );
31
+ return Object.fromEntries(
32
+ Object.entries(value).map(([key, item]) => [
33
+ key,
34
+ jsonOutput(item, `${path}.${key}`, seen),
35
+ ]),
36
+ );
37
+ } finally {
38
+ seen.delete(value);
39
+ }
40
+ }
41
+
42
+ export function codeJudgment(output: JsonValue): Judgment {
43
+ const values =
44
+ output &&
45
+ typeof output === "object" &&
46
+ !Array.isArray(output) &&
47
+ "scores" in output
48
+ ? output.scores
49
+ : {};
50
+ if (!values || typeof values !== "object" || Array.isArray(values))
51
+ throw new Error("output.scores must be an object keyed by criterion ID");
52
+ const scores = Object.fromEntries(
53
+ Object.entries(values).map(([id, supplied]): [string, CriterionScore] => {
54
+ if (!/^[a-z][a-z0-9_]*$/.test(id))
55
+ throw new Error(`Invalid criterion ID: ${id}`);
56
+ const value = typeof supplied === "boolean" ? Number(supplied) : supplied;
57
+ if (value !== null && !isScored(value))
58
+ throw new Error(
59
+ `scores.${id} must be a boolean, a finite number from 0 to 1, or null`,
60
+ );
61
+ return [
62
+ id,
63
+ {
64
+ value: value as number | null,
65
+ reason:
66
+ value === null
67
+ ? "judge.ts returned an unresolved score."
68
+ : `judge.ts returned ${String(supplied)}.`,
69
+ evidence: value === null ? [] : [{ kind: "recording" }],
70
+ source: "judge.ts",
71
+ },
72
+ ];
73
+ }),
74
+ );
75
+ return {
76
+ scores,
77
+ value: criterionMean(scores),
78
+ reason: Object.keys(scores).length
79
+ ? "Criterion scores computed by judge.ts."
80
+ : "The code judge returned data without criterion scores.",
81
+ };
82
+ }
83
+
84
+ export function combineJudgments(...judgments: Judgment[]): Judgment {
85
+ const scores: Record<string, CriterionScore> = {};
86
+ for (const judgment of judgments)
87
+ for (const [id, score] of Object.entries(judgment.scores)) {
88
+ if (Object.hasOwn(scores, id))
89
+ throw new Error(
90
+ `Duplicate criterion ID from judge.md and judge.ts: ${id}`,
91
+ );
92
+ scores[id] = score;
93
+ }
94
+ return {
95
+ scores,
96
+ value: criterionMean(scores),
97
+ reason: judgments
98
+ .map((judgment) => judgment.reason)
99
+ .filter(Boolean)
100
+ .join("\n"),
101
+ };
102
+ }
@@ -0,0 +1,96 @@
1
+ import { dirname, isAbsolute, resolve } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import type { CodeJudgeDefinition } from "../../judge-context";
4
+ import { fingerprint } from "../files";
5
+
6
+ /** Bundle local imports without executing author code during planning. */
7
+ export async function compileCodeJudge(
8
+ file: string,
9
+ ): Promise<CodeJudgeDefinition> {
10
+ file = resolve(file);
11
+ const dependencies: Record<string, string> = {};
12
+ const sdkManifest = fileURLToPath(
13
+ new URL("../../../package.json", import.meta.url),
14
+ );
15
+ dependencies[sdkManifest] = await Bun.file(sdkManifest).text();
16
+ const external = new Set<string>();
17
+ const built = await Bun.build({
18
+ entrypoints: [file],
19
+ root: dirname(file),
20
+ target: "bun",
21
+ format: "esm",
22
+ packages: "external",
23
+ sourcemap: "external",
24
+ plugins: [
25
+ {
26
+ name: "judge-dependencies",
27
+ setup(build) {
28
+ build.onResolve({ filter: /^[^./]/ }, (args) => {
29
+ if (
30
+ args.path.startsWith("node:") ||
31
+ args.path.startsWith("bun:") ||
32
+ isAbsolute(args.path)
33
+ )
34
+ return;
35
+ const path = Bun.resolveSync(
36
+ args.path,
37
+ args.resolveDir || dirname(file),
38
+ );
39
+ external.add(path);
40
+ return { path, external: true };
41
+ });
42
+ },
43
+ },
44
+ ],
45
+ });
46
+ if (!built.success)
47
+ throw new Error(`Cannot compile judge.ts: ${built.logs.join("\n")}`);
48
+ const source = await built.outputs
49
+ .find((output) => output.kind === "entry-point")!
50
+ .text();
51
+ if (
52
+ !new Bun.Transpiler({ loader: "js" })
53
+ .scan(source)
54
+ .exports.includes("default")
55
+ )
56
+ throw new Error("judge.ts must have a default function export");
57
+ const sourceMap =
58
+ (await built.outputs
59
+ .find((output) => output.kind === "sourcemap")
60
+ ?.text()) ?? "";
61
+ const collect = async (directory: string, stopAtPackage: boolean) => {
62
+ for (;;) {
63
+ let found = false;
64
+ for (const name of [
65
+ "package.json",
66
+ "bun.lock",
67
+ "bun.lockb",
68
+ "package-lock.json",
69
+ "pnpm-lock.yaml",
70
+ "yarn.lock",
71
+ ]) {
72
+ const path = resolve(directory, name);
73
+ if (await Bun.file(path).exists()) {
74
+ dependencies[path] =
75
+ name === "bun.lockb"
76
+ ? Buffer.from(await Bun.file(path).bytes()).toString("base64")
77
+ : await Bun.file(path).text();
78
+ found ||= name === "package.json";
79
+ }
80
+ }
81
+ if (found && stopAtPackage) return;
82
+ const parent = dirname(directory);
83
+ if (parent === directory) return;
84
+ directory = parent;
85
+ }
86
+ };
87
+ await collect(dirname(file), false);
88
+ for (const path of external) await collect(dirname(path), true);
89
+ return {
90
+ file,
91
+ source,
92
+ sourceMap,
93
+ dependencies,
94
+ hash: fingerprint({ source, sourceMap, dependencies }),
95
+ };
96
+ }
@@ -0,0 +1,26 @@
1
+ import { pathToFileURL } from "node:url";
2
+ import { openRecording, type RecordingInput } from "../recording";
3
+ import { jsonOutput } from "./code-result";
4
+ import { errorMessage, writeJson } from "../files";
5
+ import type { JudgeFunction, RunMetrics } from "../../judge-context";
6
+
7
+ type Job = RecordingInput & { module: string; output: string; scratch: string };
8
+ const job: Job = JSON.parse(await Bun.stdin.text());
9
+ let metrics: RunMetrics | undefined;
10
+ async function grade() {
11
+ await using context = await openRecording(job, job.scratch);
12
+ metrics = context.metrics;
13
+ const module = await import(pathToFileURL(job.module).href);
14
+ if (typeof module.default !== "function")
15
+ throw new Error("judge.ts must default-export a function");
16
+ const output = jsonOutput(await (module.default as JudgeFunction)(context));
17
+ return { output, metrics: context.metrics };
18
+ }
19
+ try {
20
+ await writeJson(job.output, await grade());
21
+ process.exit(0);
22
+ } catch (error) {
23
+ await writeJson(job.output, { error: errorMessage(error), metrics });
24
+ console.error(error);
25
+ process.exit(1);
26
+ }
@@ -0,0 +1,97 @@
1
+ import { mkdir, rm } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import type {
5
+ CodeJudgeDefinition,
6
+ CodeJudgeExecution,
7
+ JsonValue,
8
+ RunMetrics,
9
+ } from "../../judge-context";
10
+ import type { RecordingInput } from "../recording";
11
+ import { codeJudgment, jsonOutput } from "./code-result";
12
+ import { errorMessage, writeJson } from "../files";
13
+
14
+ /** A separate process bounds synchronous loops as well as asynchronous judges. */
15
+ export async function executeCodeJudge(
16
+ code: CodeJudgeDefinition,
17
+ recording: RecordingInput,
18
+ directory: string,
19
+ timeoutMs: number,
20
+ ): Promise<CodeJudgeExecution> {
21
+ directory = resolve(directory);
22
+ await mkdir(directory, { recursive: true });
23
+ const module = resolve(directory, "judge.js"),
24
+ output = resolve(directory, "output.json");
25
+ const scratch = resolve(directory, "scratch");
26
+ const stdout = resolve(directory, "stdout.log"),
27
+ stderr = resolve(directory, "stderr.log");
28
+ await Bun.write(module, code.source);
29
+ await Bun.write(resolve(directory, "judge.js.map"), code.sourceMap);
30
+ await writeJson(resolve(directory, "dependencies.json"), code.dependencies);
31
+ const startedAt = new Date().toISOString();
32
+ let timer: ReturnType<typeof setTimeout> | undefined;
33
+ let timedOut = false;
34
+ let raw: JsonValue | undefined, metrics: RunMetrics | undefined;
35
+ const base = { startedAt, sourceHash: code.hash, stdout, stderr };
36
+ try {
37
+ const child = Bun.spawn(
38
+ [
39
+ process.execPath,
40
+ fileURLToPath(new URL("./code-worker.ts", import.meta.url)),
41
+ ],
42
+ {
43
+ cwd: directory,
44
+ stdin: "pipe",
45
+ stdout: Bun.file(stdout),
46
+ stderr: Bun.file(stderr),
47
+ windowsHide: true,
48
+ },
49
+ );
50
+ timer = setTimeout(() => {
51
+ timedOut = true;
52
+ child.kill();
53
+ }, timeoutMs);
54
+ await child.stdin.write(
55
+ JSON.stringify({ ...recording, module, output, scratch }),
56
+ );
57
+ child.stdin.end();
58
+ const exit = await child.exited;
59
+ if (timedOut)
60
+ throw new Error(`judge.ts exceeded its ${timeoutMs}ms time limit`);
61
+ const result = await Bun.file(output).json();
62
+ metrics = result.metrics;
63
+ if (exit !== 0 || result.error)
64
+ throw new Error(result.error ?? `judge.ts exited with code ${exit}`);
65
+ raw = jsonOutput(result.output);
66
+ const judgment = codeJudgment(raw);
67
+ return {
68
+ ...base,
69
+ state: "completed",
70
+ completedAt: new Date().toISOString(),
71
+ elapsedMs: Date.now() - Date.parse(startedAt),
72
+ output: raw,
73
+ metrics,
74
+ scores: Object.fromEntries(
75
+ Object.entries(judgment.scores).map(([id, score]) => [id, score.value]),
76
+ ),
77
+ };
78
+ } catch (error) {
79
+ return {
80
+ ...base,
81
+ state: timedOut ? "timed_out" : "failed",
82
+ completedAt: new Date().toISOString(),
83
+ elapsedMs: Date.now() - Date.parse(startedAt),
84
+ error: errorMessage(error),
85
+ output: raw,
86
+ metrics,
87
+ };
88
+ } finally {
89
+ clearTimeout(timer);
90
+ await rm(scratch, {
91
+ recursive: true,
92
+ force: true,
93
+ maxRetries: 5,
94
+ retryDelay: 100,
95
+ });
96
+ }
97
+ }
@@ -1,11 +1,11 @@
1
1
  import type {
2
2
  EvidenceCitation,
3
3
  Judgment,
4
- MetricDefinition,
5
- MetricJudgment,
4
+ CriterionDefinition,
5
+ CriterionScore,
6
6
  } from "../../types";
7
7
  import type { EvidenceReader, EvidenceQuery } from "../../evidence";
8
- import { metricMean } from "../../judgment";
8
+ import { criterionMean, isScored } from "../../judgment";
9
9
  import { Schema } from "effect";
10
10
 
11
11
  export const STRICT_OBJECT = {
@@ -14,68 +14,77 @@ export const STRICT_OBJECT = {
14
14
 
15
15
  const object = (value: unknown): value is Record<string, unknown> =>
16
16
  !!value && typeof value === "object" && !Array.isArray(value);
17
- const binary = (value: unknown) => value === 0 || value === 1 || value === null;
17
+ const validScore = (value: unknown) =>
18
+ value === null || typeof value === "boolean" || isScored(value);
18
19
  const reason = (value: unknown): value is string =>
19
20
  typeof value === "string" && !!value.trim();
20
21
 
21
- /** Validate structured tool arguments; metric meaning remains the judge's job. */
22
- export function validateMetrics(
22
+ /** Validate structured tool arguments; criterion meaning remains the judge's job. */
23
+ export function validateCriteria(
23
24
  value: unknown,
24
- expected: readonly MetricDefinition[],
25
+ expected: readonly CriterionDefinition[],
25
26
  ): Judgment {
26
27
  if (!object(value))
27
- throw new Error("metrics must be an object keyed by metric ID");
28
+ throw new Error("scores must be an object keyed by criterion ID");
28
29
  const ids = Object.keys(value);
29
30
  if (
30
31
  ids.length !== expected.length ||
31
- ids.some((id) => !expected.some((metric) => metric.id === id))
32
+ ids.some((id) => !expected.some((criterion) => criterion.id === id))
32
33
  )
33
34
  throw new Error(
34
- `Submit exactly these metric IDs: ${expected.map((metric) => metric.id).join(", ")}`,
35
+ `Submit exactly these criterion IDs: ${expected.map((criterion) => criterion.id).join(", ")}`,
35
36
  );
36
- const metrics = expected.map(({ id }): MetricJudgment => {
37
- const item = value[id];
38
- if (
39
- !object(item) ||
40
- !binary(item.value) ||
41
- !reason(item.reason) ||
42
- !Array.isArray(item.evidence) ||
43
- item.evidence.length > 32 ||
44
- (item.value !== null && !item.evidence.length)
45
- )
46
- throw new Error(
47
- `Metric ${id} requires value 0, 1, or null, a non-empty reason, and 1–32 citations when decided`,
48
- );
49
- const evidence = item.evidence.map(parseCitation);
50
- if (
51
- item.sources !== undefined &&
52
- (!Array.isArray(item.sources) ||
53
- !item.sources.every(
54
- (url) => typeof url === "string" && /^https?:\/\//.test(url),
55
- ))
56
- )
57
- throw new Error("Metric sources must be HTTP(S) URLs");
58
- return {
59
- id,
60
- value: item.value as 0 | 1 | null,
61
- reason: item.reason,
62
- evidence,
63
- ...(item.sources ? { sources: [...(item.sources as string[])] } : {}),
64
- };
65
- });
37
+ const scores = Object.fromEntries(
38
+ expected.map(({ id }): [string, CriterionScore] => {
39
+ const item = value[id];
40
+ if (
41
+ !object(item) ||
42
+ !validScore(item.value) ||
43
+ !reason(item.reason) ||
44
+ !Array.isArray(item.evidence) ||
45
+ item.evidence.length > 32 ||
46
+ (item.value !== null && !item.evidence.length)
47
+ )
48
+ throw new Error(
49
+ `Criterion ${id} requires a boolean, a value from 0 to 1, or null, a non-empty reason, and 1–32 citations when decided`,
50
+ );
51
+ const evidence = item.evidence.map(parseCitation);
52
+ if (
53
+ item.sources !== undefined &&
54
+ (!Array.isArray(item.sources) ||
55
+ !item.sources.every(
56
+ (url) => typeof url === "string" && /^https?:\/\//.test(url),
57
+ ))
58
+ )
59
+ throw new Error("Criterion sources must be HTTP(S) URLs");
60
+ return [
61
+ id,
62
+ {
63
+ value:
64
+ typeof item.value === "boolean"
65
+ ? Number(item.value)
66
+ : (item.value as number | null),
67
+ reason: item.reason,
68
+ evidence,
69
+ source: "judge.md",
70
+ ...(item.sources ? { sources: [...(item.sources as string[])] } : {}),
71
+ },
72
+ ];
73
+ }),
74
+ );
66
75
  return {
67
- value: metricMean(metrics),
68
- reason: metrics
69
- .map((metric) => `${metric.id}: ${metric.reason}`)
76
+ value: criterionMean(scores),
77
+ reason: Object.entries(scores)
78
+ .map(([id, score]) => `${id}: ${score.reason}`)
70
79
  .join("\n"),
71
- metrics,
80
+ scores,
72
81
  };
73
82
  }
74
83
 
75
84
  function parseCitation(value: unknown): EvidenceCitation {
76
85
  if (
77
86
  !object(value) ||
78
- !["response", "tool", "message", "event", "artifact"].includes(
87
+ !["response", "tool", "message", "event", "artifact", "metric"].includes(
79
88
  String(value.kind),
80
89
  )
81
90
  )
@@ -110,7 +119,7 @@ function parseCitation(value: unknown): EvidenceCitation {
110
119
  }
111
120
  if (!reason(value.id)) throw new Error("Citation requires a recorded ID");
112
121
  return {
113
- kind: value.kind as "tool" | "message" | "event",
122
+ kind: value.kind as "tool" | "message" | "event" | "metric",
114
123
  id: value.id,
115
124
  ...extra,
116
125
  };
@@ -123,18 +132,22 @@ export async function validateCitations(
123
132
  judgment: Judgment,
124
133
  reader: EvidenceReader,
125
134
  ) {
126
- for (const metric of judgment.metrics)
127
- for (const citation of metric.evidence) {
135
+ for (const [id, score] of Object.entries(judgment.scores))
136
+ for (const citation of score.evidence) {
128
137
  const query: EvidenceQuery =
129
- citation.kind === "artifact"
130
- ? {
131
- action: "artifact",
132
- path: citation.path,
133
- revision: citation.revision,
134
- }
135
- : citation.kind === "response"
136
- ? { action: "response" }
137
- : { action: citation.kind, id: citation.id };
138
+ citation.kind === "recording"
139
+ ? { action: "summary" }
140
+ : citation.kind === "metric"
141
+ ? { action: "metrics", metric: citation.id }
142
+ : citation.kind === "artifact"
143
+ ? {
144
+ action: "artifact",
145
+ path: citation.path,
146
+ revision: citation.revision,
147
+ }
148
+ : citation.kind === "response"
149
+ ? { action: "response" }
150
+ : { action: citation.kind, id: citation.id };
138
151
  let offset = citation.offset ?? 0,
139
152
  found = false;
140
153
  for (let page = 0; page < 100; page++) {
@@ -154,12 +167,12 @@ export async function validateCitations(
154
167
  );
155
168
  }
156
169
  if (!found)
157
- throw new Error(`Citation quote was not found for metric ${metric.id}`);
170
+ throw new Error(`Citation quote was not found for criterion ${id}`);
158
171
  }
159
172
  }
160
173
 
161
174
  /** The native tool validator exposes these shapes directly in Code Mode. */
162
- export function metricsSchema(metrics: readonly MetricDefinition[]) {
175
+ export function criteriaSchema(criteria: readonly CriterionDefinition[]) {
163
176
  const citationFields = {
164
177
  quote: Schema.optional(Schema.String.check(Schema.isMaxLength(2000))),
165
178
  offset: Schema.optional(
@@ -175,7 +188,7 @@ export function metricsSchema(metrics: readonly MetricDefinition[]) {
175
188
  }).annotate(STRICT_OBJECT),
176
189
  Schema.Struct({
177
190
  ...citationFields,
178
- kind: Schema.Literals(["tool", "message", "event"]),
191
+ kind: Schema.Literals(["tool", "message", "event", "metric"]),
179
192
  id: Schema.NonEmptyString,
180
193
  }).annotate(STRICT_OBJECT),
181
194
  Schema.Struct({
@@ -186,7 +199,12 @@ export function metricsSchema(metrics: readonly MetricDefinition[]) {
186
199
  }).annotate(STRICT_OBJECT),
187
200
  ]);
188
201
  const decision = Schema.Struct({
189
- value: Schema.NullOr(Schema.Literals([0, 1])),
202
+ value: Schema.NullOr(
203
+ Schema.Union([
204
+ Schema.Boolean,
205
+ Schema.Number.check(Schema.isBetween({ minimum: 0, maximum: 1 })),
206
+ ]),
207
+ ),
190
208
  reason: Schema.NonEmptyString,
191
209
  evidence: Schema.Array(citation).check(
192
210
  Schema.isMinLength(0),
@@ -198,9 +216,9 @@ export function metricsSchema(metrics: readonly MetricDefinition[]) {
198
216
  }).annotate(STRICT_OBJECT);
199
217
  return Schema.Struct(
200
218
  Object.fromEntries(
201
- metrics.map((metric) => [
202
- metric.id,
203
- decision.annotate({ description: metric.name }),
219
+ criteria.map((criterion) => [
220
+ criterion.id,
221
+ decision.annotate({ description: criterion.name }),
204
222
  ]),
205
223
  ),
206
224
  ).annotate(STRICT_OBJECT);