@hona/openeval 0.2.1 → 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.
- package/JUDGING.md +134 -23
- package/README.md +79 -9
- package/package.json +3 -2
- package/src/app/cost-plan.ts +20 -16
- package/src/app/eval-state.ts +2 -1
- package/src/app/grade-recording.ts +72 -0
- package/src/app/input-fingerprints.ts +22 -8
- package/src/app/judge-evidence.ts +26 -11
- package/src/app/judge-run.ts +36 -19
- package/src/app/load-benchmark.ts +46 -15
- package/src/app/read-results.ts +35 -2
- package/src/app/read-run.ts +23 -2
- package/src/app/rejudge.ts +2 -1
- package/src/app/remove-models.ts +60 -0
- package/src/app/run-benchmark.ts +4 -0
- package/src/app/run-eval-pipeline.ts +2 -1
- package/src/app/run-eval.ts +13 -0
- package/src/app/scores.ts +76 -34
- package/src/app/serve-results.ts +2 -0
- package/src/cli.ts +14 -2
- package/src/evidence.ts +4 -1
- package/src/index.ts +16 -3
- package/src/infra/evidence/index.ts +39 -2
- package/src/infra/evidence/tool-calls.ts +5 -2
- package/src/infra/judging/agent.ts +3 -2
- package/src/infra/judging/code-result.ts +102 -0
- package/src/infra/judging/code-source.ts +96 -0
- package/src/infra/judging/code-worker.ts +26 -0
- package/src/infra/judging/code.ts +97 -0
- package/src/infra/judging/contract.ts +82 -64
- package/src/infra/judging/evidence-tool.ts +3 -1
- package/src/infra/judging/index.ts +14 -0
- package/src/infra/judging/judge-agent.md +24 -18
- package/src/infra/judging/observer.ts +8 -7
- package/src/infra/judging/submission.ts +8 -8
- package/src/infra/judging/tools.ts +9 -9
- package/src/infra/opencode/host.ts +1 -1
- package/src/infra/opencode/read-recording.ts +6 -1
- package/src/infra/opencode/version.ts +1 -0
- package/src/infra/recording/index.ts +244 -0
- package/src/infra/recording/metrics.ts +193 -0
- package/src/infra/sqlite/index.ts +29 -19
- package/src/judge-context.ts +141 -0
- package/src/judgment.ts +26 -17
- package/src/types.ts +36 -17
- package/src/view.ts +40 -14
- package/viewer/assets/{abnfDiagram-VCTEODGH-B1kcYgQG.js → abnfDiagram-VCTEODGH-rgmalTBh.js} +1 -1
- package/viewer/assets/{angular-html-DrYCUiZv.js → angular-html-CcaajvLj.js} +1 -1
- package/viewer/assets/{angular-ts-BgjwQn-Z.js → angular-ts-COx6AtG2.js} +1 -1
- package/viewer/assets/{apl-DlHqe8o4.js → apl-lBEnC_vz.js} +1 -1
- package/viewer/assets/{arc-C4nf7ZoY.js → arc-BQ0cQOwK.js} +1 -1
- package/viewer/assets/architecture-7GRP2DOG-DcUKSnYa.js +1 -0
- package/viewer/assets/{architectureDiagram-5GKGNRK7-BIWoe0fO.js → architectureDiagram-5GKGNRK7-_XIJP-LV.js} +1 -1
- package/viewer/assets/{astro-BrlHnIh9.js → astro-Ca8FrXna.js} +1 -1
- package/viewer/assets/{blade-DOcerOiW.js → blade-B07ZVjUX.js} +1 -1
- package/viewer/assets/{blockDiagram-I7D4REHJ-C3x13a9m.js → blockDiagram-I7D4REHJ-BdcDRYB-.js} +1 -1
- package/viewer/assets/{c-BMbThxEo.js → c-4MsqXzJK.js} +1 -1
- package/viewer/assets/{c4Diagram-7LVT6UL2-BeWktchx.js → c4Diagram-7LVT6UL2-E3nvwqBd.js} +1 -1
- package/viewer/assets/channel-CViXUg9J.js +1 -0
- package/viewer/assets/{chapel-k2cjkSmc.js → chapel-BlwR_8zu.js} +1 -1
- package/viewer/assets/{chunk-4HAMMTFA-o6YtJsqE.js → chunk-4HAMMTFA-DN_vtpqx.js} +1 -1
- package/viewer/assets/{chunk-75Z2AOVW-KohQa9Nw.js → chunk-75Z2AOVW-DWMGmicp.js} +1 -1
- package/viewer/assets/{chunk-DU6HZSFF-C88wxc6m.js → chunk-DU6HZSFF-D2RGHn75.js} +1 -1
- package/viewer/assets/{chunk-F27PBJKO-mO4AP2as.js → chunk-F27PBJKO-wfQZ5Ilx.js} +1 -1
- package/viewer/assets/{chunk-GMAD6QVW-DzQX2Cld.js → chunk-GMAD6QVW-DrlfUg7k.js} +1 -1
- package/viewer/assets/{chunk-GVQU2GXP-CS78RfJD.js → chunk-GVQU2GXP-BHoRLKIU.js} +1 -1
- package/viewer/assets/{chunk-IMKFNOWR-DxS42GUo.js → chunk-IMKFNOWR-mxLyQhYU.js} +1 -1
- package/viewer/assets/{chunk-L3NEJ4N5-DGo-uYLy.js → chunk-L3NEJ4N5-IpGl-pvp.js} +1 -1
- package/viewer/assets/{chunk-OSK3NFVY-BOcXQG16.js → chunk-OSK3NFVY-t0yN50X6.js} +1 -1
- package/viewer/assets/{chunk-P2QGCYS3-B7GnZdIQ.js → chunk-P2QGCYS3-BPBYYOcK.js} +1 -1
- package/viewer/assets/{chunk-POPQ4Y6H-DgBcYHog.js → chunk-POPQ4Y6H-DIOcv86B.js} +1 -1
- package/viewer/assets/{chunk-PWAF6VOD-DpEq6qHA.js → chunk-PWAF6VOD-CqVB-mzq.js} +1 -1
- package/viewer/assets/{chunk-SHT3W25Y-CrOGxKM2.js → chunk-SHT3W25Y-DwOEgFIr.js} +1 -1
- package/viewer/assets/{chunk-SVP7TREG-4HY7Fljj.js → chunk-SVP7TREG-Dfg1jHc3.js} +1 -1
- package/viewer/assets/{chunk-TICWLB2K-B1Rl31EC.js → chunk-TICWLB2K-DE6tQNRu.js} +1 -1
- package/viewer/assets/{chunk-XXDRQBXY-CK8giEW-.js → chunk-XXDRQBXY-Bu8dW06M.js} +1 -1
- package/viewer/assets/classDiagram-ZZMXUADV-O1Zm0_p8.js +1 -0
- package/viewer/assets/classDiagram-v2-VYDZK3BY-O1Zm0_p8.js +1 -0
- package/viewer/assets/{cobol-CupUwvw9.js → cobol-BS_DrT-b.js} +1 -1
- package/viewer/assets/{coffee-C17pkozF.js → coffee-Clk97ecB.js} +1 -1
- package/viewer/assets/{cose-bilkent-JH36ORCC-V1OmrmCv.js → cose-bilkent-JH36ORCC-B3xA8n2e.js} +1 -1
- package/viewer/assets/{cpp-BJwVQVXg.js → cpp-aunMdKLY.js} +1 -1
- package/viewer/assets/{crystal-BF-oCN-L.js → crystal-B1oihgG5.js} +1 -1
- package/viewer/assets/{css-BMgkVI3c.js → css-ClOubSVo.js} +1 -1
- package/viewer/assets/{cynefin-OW5HDTMX-BEjY_gsc.js → cynefin-OW5HDTMX-DdEeq9jN.js} +1 -1
- package/viewer/assets/{cynefinDiagram-5FMLGOSQ-bfSYgr6z.js → cynefinDiagram-5FMLGOSQ-BwHCV_5V.js} +1 -1
- package/viewer/assets/{dagre-GXQ25YYZ-VtwTgGfa.js → dagre-GXQ25YYZ-BkuWRmq-.js} +1 -1
- package/viewer/assets/{diagram-S7CK7UJ4-CHaAAyW7.js → diagram-S7CK7UJ4-C5WGyCny.js} +1 -1
- package/viewer/assets/{diagram-UQ7AKVKN-CjL65-As.js → diagram-UQ7AKVKN-mdununKH.js} +1 -1
- package/viewer/assets/{diagram-VSXAHHWV-Cnxrk1TJ.js → diagram-VSXAHHWV-CpuvU9Ij.js} +1 -1
- package/viewer/assets/{diagram-VX7I27RA-D78jPXr7.js → diagram-VX7I27RA-CcLmTPwv.js} +1 -1
- package/viewer/assets/{diagram-Z3DM3KII-DKjnOaF2.js → diagram-Z3DM3KII-DWCnKyKY.js} +1 -1
- package/viewer/assets/{dist-BTYPW0dz.js → dist-1DE7d697.js} +1 -1
- package/viewer/assets/{ebnfDiagram-PWID7BFC-7XiXh5Lx.js → ebnfDiagram-PWID7BFC-9M_-_M-B.js} +1 -1
- package/viewer/assets/{edge-B34N_HjN.js → edge-NsPpWeFX.js} +1 -1
- package/viewer/assets/{elixir-CQzWMyKD.js → elixir-CXZ4zQTn.js} +1 -1
- package/viewer/assets/{elm-BNJiG-wg.js → elm-DVXugwZs.js} +1 -1
- package/viewer/assets/{erDiagram-RLTQ6QDP-YPFEgZxU.js → erDiagram-RLTQ6QDP-B2j6iCkZ.js} +1 -1
- package/viewer/assets/{erb-D5Rm3UNa.js → erb-h8ybsnSQ.js} +1 -1
- package/viewer/assets/eventmodeling-NTZA5JFV-CpMf6avu.js +1 -0
- package/viewer/assets/flowDiagram-HODETNUW-DIKEcF1k.js +1 -0
- package/viewer/assets/{ganttDiagram-EL5Y4UJY-ergSGtTP.js → ganttDiagram-EL5Y4UJY-CR2QLJ-I.js} +1 -1
- package/viewer/assets/{git-rebase-BpIMZIRG.js → git-rebase-CT9f4kn8.js} +1 -1
- package/viewer/assets/{gitGraph-4MIJSDKK-CZt4REVj.js → gitGraph-4MIJSDKK-DGw-0dqe.js} +1 -1
- package/viewer/assets/{gitGraphDiagram-WWUBYQGX-h9QMzC2r.js → gitGraphDiagram-WWUBYQGX-BW112jVV.js} +1 -1
- package/viewer/assets/{glimmer-js-SeSwns5_.js → glimmer-js-B17pi1gL.js} +1 -1
- package/viewer/assets/{glimmer-ts-BExI5tTz.js → glimmer-ts-CNmwRbOn.js} +1 -1
- package/viewer/assets/{glsl-CXikKw2b.js → glsl-By4kbzYa.js} +1 -1
- package/viewer/assets/{graphql-6GpQIInt.js → graphql-BrDJs0bG.js} +1 -1
- package/viewer/assets/{hack-i7rizvpB.js → hack-NosEIqRi.js} +1 -1
- package/viewer/assets/{haml-C68gmkTf.js → haml-B-GQD8Zj.js} +1 -1
- package/viewer/assets/{handlebars-BBn_c0Xt.js → handlebars-1vSTccT5.js} +1 -1
- package/viewer/assets/{html-BObmAZQA.js → html-BBEodwWE.js} +1 -1
- package/viewer/assets/{html-derivative-BjMuR1LM.js → html-derivative-ClDEAvlc.js} +1 -1
- package/viewer/assets/{http-BJBk8gt0.js → http-I0uH89QQ.js} +1 -1
- package/viewer/assets/{hurl-D7obCYfg.js → hurl-JU97iWqA.js} +1 -1
- package/viewer/assets/{index-FMtYXcX7.js → index-CXBRQlCH.js} +5 -5
- package/viewer/assets/{index-Wz0qQyIk.css → index-D6HZB4Yz.css} +1 -1
- package/viewer/assets/{info-A6RAGUB7-CrzitQIU.js → info-A6RAGUB7-aRrZ0_i2.js} +1 -1
- package/viewer/assets/{infoDiagram-27XIBGKW-D2CgRDxu.js → infoDiagram-27XIBGKW-BnD3mUdL.js} +1 -1
- package/viewer/assets/{ishikawaDiagram-5VMMS53U-BhnQMHOo.js → ishikawaDiagram-5VMMS53U-u-wcafFg.js} +1 -1
- package/viewer/assets/{java-5VJU_EW8.js → java-BxAxXJLN.js} +1 -1
- package/viewer/assets/{javascript-r8UgndxQ.js → javascript-DNepMMwm.js} +1 -1
- package/viewer/assets/{jinja-BY2ibHG5.js → jinja-CfKdKg9T.js} +1 -1
- package/viewer/assets/{jison-C_7YxymZ.js → jison-C62ElGA9.js} +1 -1
- package/viewer/assets/{journeyDiagram-3NMN7TZE-DSBzKQEa.js → journeyDiagram-3NMN7TZE-E6uhr_fk.js} +1 -1
- package/viewer/assets/{json-8e1WlJYD.js → json-DSQqeQzH.js} +1 -1
- package/viewer/assets/{jsx-BYp5GD4Y.js → jsx-CpZi9Ceb.js} +1 -1
- package/viewer/assets/{julia-Bkob1uVX.js → julia-CypyJgFE.js} +1 -1
- package/viewer/assets/{just-CUnSjty-.js → just-_p8XAy70.js} +1 -1
- package/viewer/assets/{kanban-definition-UXKFOSKX-CotbrQue.js → kanban-definition-UXKFOSKX-Blo6hDj7.js} +1 -1
- package/viewer/assets/{latex-UdE3ivqk.js → latex-Blby6_jP.js} +1 -1
- package/viewer/assets/{line-DDh6a6hz.js → line-CQc5Trjj.js} +1 -1
- package/viewer/assets/{linear-BxMsCbKb.js → linear-BjWjnaEC.js} +1 -1
- package/viewer/assets/{liquid-WL4MvEIT.js → liquid-x3m9Bx2x.js} +1 -1
- package/viewer/assets/{lua-CtOubZta.js → lua-Da9XAgwV.js} +1 -1
- package/viewer/assets/{marko-C0GSQJi3.js → marko-Gu1d_h3t.js} +1 -1
- package/viewer/assets/{mdc-DUivddFc.js → mdc-CM0pUbEe.js} +1 -1
- package/viewer/assets/{mermaid-parser.core-FhKE_uv2.js → mermaid-parser.core-DR191xUW.js} +3 -3
- package/viewer/assets/{mermaid.core-BSOWwyvd.js → mermaid.core-DnwMH2U1.js} +4 -4
- package/viewer/assets/{mindmap-definition-YA3MSWOX-tsLZ_Opn.js → mindmap-definition-YA3MSWOX-KrnPcZeq.js} +1 -1
- package/viewer/assets/{nginx-B__INAtC.js → nginx-DAKCD0HY.js} +1 -1
- package/viewer/assets/{nim-UEY12GJF.js → nim-BvNTuhqF.js} +1 -1
- package/viewer/assets/{org-DDIZsz95.js → org-B-tOc6Cc.js} +1 -1
- package/viewer/assets/{packet-AYTQ26CC-CqdJnqfo.js → packet-AYTQ26CC-Cl2KBEou.js} +1 -1
- package/viewer/assets/{pegDiagram-XKGWAZYB-BJs7qu7m.js → pegDiagram-XKGWAZYB--GvmNzTl.js} +1 -1
- package/viewer/assets/{perl-D5KwNZt7.js → perl-RyNpCOE-.js} +1 -1
- package/viewer/assets/{php-BEs7d_Dk.js → php-D5gke6Nh.js} +1 -1
- package/viewer/assets/{pie-WAS4IAKB-lgzqBWqy.js → pie-WAS4IAKB-RfZPDbVI.js} +1 -1
- package/viewer/assets/{pieDiagram-E7YTZNPT-Cxe9LnnE.js → pieDiagram-E7YTZNPT-BbcD9IEY.js} +1 -1
- package/viewer/assets/{pug-B0dsfQOV.js → pug-CSZdPWzF.js} +1 -1
- package/viewer/assets/{qml-XPIhrIzf.js → qml-CCUSNo18.js} +1 -1
- package/viewer/assets/{quadrantDiagram-AXDQQJYC-D5JGwC11.js → quadrantDiagram-AXDQQJYC-DHP-nbSh.js} +1 -1
- package/viewer/assets/{r-C6hQdpIM.js → r-Cl6F5Lf8.js} +1 -1
- package/viewer/assets/{radar-RG4KPBEZ-B7JRLhUA.js → radar-RG4KPBEZ-DdfC1yOQ.js} +1 -1
- package/viewer/assets/{railroad-74A4TZTK-Df7ZQBLn.js → railroad-74A4TZTK-MHxcKgVS.js} +1 -1
- package/viewer/assets/railroad-abnf-HS5TGJTU-yEJXYTCu.js +1 -0
- package/viewer/assets/railroad-ebnf-LZEXJU2U-CEc058YK.js +1 -0
- package/viewer/assets/railroad-peg-WCYAUIDC-0iFSHGbV.js +1 -0
- package/viewer/assets/{railroadDiagram-O6MQD6OU-DZJzpmP8.js → railroadDiagram-O6MQD6OU-sxdxPqy-.js} +1 -1
- package/viewer/assets/{razor-C3KSUuOw.js → razor-hm0vLE-h.js} +1 -1
- package/viewer/assets/{regexp-n-T936Rk.js → regexp-BhIot3VY.js} +1 -1
- package/viewer/assets/{requirementDiagram-BXWQKSXE-Be9lEFrk.js → requirementDiagram-BXWQKSXE-CrZXt7Yu.js} +1 -1
- package/viewer/assets/{rst-fVnHDyXD.js → rst-C3giCu1C.js} +1 -1
- package/viewer/assets/{ruby-DuRgxlXF.js → ruby-B6fOYc5V.js} +1 -1
- package/viewer/assets/{sankeyDiagram-P5KCCOFB-BsgKAEBN.js → sankeyDiagram-P5KCCOFB-BVK_LjzN.js} +1 -1
- package/viewer/assets/{sas-C0WA2OFa.js → sas-a9NUQlsy.js} +1 -1
- package/viewer/assets/{scss-CQDZ4smR.js → scss-ChVMSp7G.js} +1 -1
- package/viewer/assets/{sequenceDiagram-WJ2MYXX4-BkUDWL0D.js → sequenceDiagram-WJ2MYXX4-B4E1wY9a.js} +1 -1
- package/viewer/assets/{shellscript-CE84G0GP.js → shellscript-Dxbnxou2.js} +1 -1
- package/viewer/assets/{shellsession-BdJvdaqL.js → shellsession-DWcRFAj_.js} +1 -1
- package/viewer/assets/{soy-BSxOREC0.js → soy-C-D-pvKx.js} +1 -1
- package/viewer/assets/{sql-D4MN9uRI.js → sql-W7nl9Rxy.js} +1 -1
- package/viewer/assets/{src-BT_kESTA.js → src-C_D1vmKd.js} +1 -1
- package/viewer/assets/{stata-B6nXAaGV.js → stata-BWq3CAeQ.js} +1 -1
- package/viewer/assets/{stateDiagram-D77RDMKH-C80wp8xG.js → stateDiagram-D77RDMKH-Dc8nXUDp.js} +1 -1
- package/viewer/assets/stateDiagram-v2-MP3YSRHH-SMxxOURe.js +1 -0
- package/viewer/assets/{surrealql-fVjZmhV3.js → surrealql-BkXUcx4E.js} +1 -1
- package/viewer/assets/{svelte-DnBQ5p7d.js → svelte-CowGUt9l.js} +1 -1
- package/viewer/assets/{swimlanes-42K2YHIH-Cff6yT8e.js → swimlanes-42K2YHIH-BtZAMLQ7.js} +1 -1
- package/viewer/assets/swimlanesDiagram-VR7AAH4N-C_XphOba.js +8 -0
- package/viewer/assets/{templ-TCx51Uts.js → templ-ejLYfFxc.js} +1 -1
- package/viewer/assets/{tex-CF6SMAQ_.js → tex-McefVK6r.js} +1 -1
- package/viewer/assets/{timeline-definition-24CTP7MA-Cje7BvaN.js → timeline-definition-24CTP7MA-TvSh-paH.js} +1 -1
- package/viewer/assets/{treeView-Q6P3EWNA-D-QLY3IO.js → treeView-Q6P3EWNA-BRIxIFdd.js} +1 -1
- package/viewer/assets/{treemap-WGGIJYW6-BCHcTa4N.js → treemap-WGGIJYW6-CeY__ApH.js} +1 -1
- package/viewer/assets/{ts-tags-BqLRnADX.js → ts-tags-D8LHJIWj.js} +1 -1
- package/viewer/assets/{tsx-D7KpDX0b.js → tsx-BkPshBHh.js} +1 -1
- package/viewer/assets/{twig-CEZLObpN.js → twig-CmfWCxbk.js} +1 -1
- package/viewer/assets/{typescript-DAbIDLbf.js → typescript-DnVmzADb.js} +1 -1
- package/viewer/assets/{typst-CqN7-whv.js → typst-BpEOIbiv.js} +1 -1
- package/viewer/assets/{vennDiagram-4TSXK5OY-Bz20rx02.js → vennDiagram-4TSXK5OY-Ba-jimVX.js} +1 -1
- package/viewer/assets/{vue-html-DQmvvKbd.js → vue-html-C-XYg42q.js} +1 -1
- package/viewer/assets/{vue-vine-Did3qTVW.js → vue-vine-DK68dTen.js} +1 -1
- package/viewer/assets/{vue-kTxN6wdc.js → vue-zII4ln6o.js} +1 -1
- package/viewer/assets/{wardley-WFR3VGLG-CpXaqOit.js → wardley-WFR3VGLG-e2A2dYzM.js} +1 -1
- package/viewer/assets/{wardleyDiagram-VM6X3IG4-Cz06jHpA.js → wardleyDiagram-VM6X3IG4-D2Rq1FsU.js} +1 -1
- package/viewer/assets/{xml-te2hFcAA.js → xml-Cc-dIbaq.js} +1 -1
- package/viewer/assets/{xsl-X4fuKSVl.js → xsl-C7A6UsbO.js} +1 -1
- package/viewer/assets/{xychartDiagram-S5SC5T6Z-BXCw0sfu.js → xychartDiagram-S5SC5T6Z-De55Qsdg.js} +1 -1
- package/viewer/assets/{yaml-NHtDSZab.js → yaml-BJLNLIRM.js} +1 -1
- package/viewer/index.html +2 -2
- package/viewer/assets/architecture-7GRP2DOG-Be250VKZ.js +0 -1
- package/viewer/assets/channel-DtULXRee.js +0 -1
- package/viewer/assets/classDiagram-ZZMXUADV-Bmqu01Xf.js +0 -1
- package/viewer/assets/classDiagram-v2-VYDZK3BY-Bmqu01Xf.js +0 -1
- package/viewer/assets/eventmodeling-NTZA5JFV-CJCg5f6p.js +0 -1
- package/viewer/assets/flowDiagram-HODETNUW-BFzq4Zvs.js +0 -1
- package/viewer/assets/railroad-abnf-HS5TGJTU-Z8q3QVTf.js +0 -1
- package/viewer/assets/railroad-ebnf-LZEXJU2U-BWoRkG3W.js +0 -1
- package/viewer/assets/railroad-peg-WCYAUIDC-BzoV1cN7.js +0 -1
- package/viewer/assets/stateDiagram-v2-MP3YSRHH-CYdsKKlz.js +0 -1
- package/viewer/assets/swimlanesDiagram-VR7AAH4N-niTUGjMT.js +0 -8
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
import { mkdir, mkdtemp, rm, symlink } from "node:fs/promises";
|
|
3
|
+
import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
4
|
+
import type { OpenCode } from "@opencode/sdk";
|
|
5
|
+
import type {
|
|
6
|
+
JudgeContext,
|
|
7
|
+
RecordedMessage,
|
|
8
|
+
Revision,
|
|
9
|
+
} from "../../judge-context";
|
|
10
|
+
import type { EvalRun, EvidenceRef } from "../../types";
|
|
11
|
+
import { CandidateEvidence } from "../evidence";
|
|
12
|
+
import { contained, hash } from "../files";
|
|
13
|
+
import { OPENCODE_VERSION } from "../opencode/version";
|
|
14
|
+
import { measureRecording } from "./metrics";
|
|
15
|
+
|
|
16
|
+
export type RecordingInput = {
|
|
17
|
+
evidence: EvidenceRef;
|
|
18
|
+
run?: EvalRun;
|
|
19
|
+
runDirectory?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** Own disposable native readers; the authoritative archive is only read. */
|
|
23
|
+
export async function openRecording(
|
|
24
|
+
input: RecordingInput,
|
|
25
|
+
workDirectory: string,
|
|
26
|
+
): Promise<JudgeContext & AsyncDisposable> {
|
|
27
|
+
const reference = {
|
|
28
|
+
...input.evidence,
|
|
29
|
+
directory: resolve(input.evidence.directory),
|
|
30
|
+
};
|
|
31
|
+
const evidence = await CandidateEvidence.open(
|
|
32
|
+
reference.directory,
|
|
33
|
+
reference.hash,
|
|
34
|
+
);
|
|
35
|
+
const events = evidence.rawEvents(),
|
|
36
|
+
tools = evidence.toolCalls(),
|
|
37
|
+
run = input.run ?? null;
|
|
38
|
+
const metrics = measureRecording(events, tools, reference);
|
|
39
|
+
const archive = run?.session;
|
|
40
|
+
const rootID = archive?.sessionId ?? null;
|
|
41
|
+
let temporary: Promise<string> | undefined,
|
|
42
|
+
database: Promise<Database> | undefined;
|
|
43
|
+
let sdk: Promise<OpenCode.Interface> | undefined;
|
|
44
|
+
const temp = () =>
|
|
45
|
+
(temporary ??= (async () => {
|
|
46
|
+
await mkdir(workDirectory, { recursive: true });
|
|
47
|
+
return mkdtemp(resolve(workDirectory, "recording-"));
|
|
48
|
+
})());
|
|
49
|
+
const copyDatabase = async (name: string) => {
|
|
50
|
+
if (!archive || !input.runDirectory)
|
|
51
|
+
throw new Error("This recording has no native OpenCode database");
|
|
52
|
+
if (archive.opencodeVersion !== OPENCODE_VERSION)
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Native reader ${OPENCODE_VERSION} cannot open archive version ${archive.opencodeVersion}`,
|
|
55
|
+
);
|
|
56
|
+
const bytes = await Bun.file(
|
|
57
|
+
resolve(input.runDirectory, archive.database),
|
|
58
|
+
).bytes();
|
|
59
|
+
if (hash(bytes) !== archive.databaseHash)
|
|
60
|
+
throw new Error("Native OpenCode database hash mismatch");
|
|
61
|
+
const path = resolve(await temp(), name);
|
|
62
|
+
await Bun.write(path, bytes);
|
|
63
|
+
return path;
|
|
64
|
+
};
|
|
65
|
+
const nativeSDK = () =>
|
|
66
|
+
(sdk ??= (async () => {
|
|
67
|
+
const path = await copyDatabase("sdk.db");
|
|
68
|
+
const { OpenCode } = await import("@opencode/sdk");
|
|
69
|
+
return OpenCode.create({
|
|
70
|
+
database: { path },
|
|
71
|
+
config: {
|
|
72
|
+
directory: resolve(dirname(path), "configuration"),
|
|
73
|
+
project: false,
|
|
74
|
+
content: JSON.stringify({ websearch: false }),
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
})());
|
|
78
|
+
const sessions = async () => {
|
|
79
|
+
const host = await nativeSDK();
|
|
80
|
+
const records = [];
|
|
81
|
+
let cursor: string | undefined;
|
|
82
|
+
do {
|
|
83
|
+
const page = await host.session.list({ limit: 100, cursor });
|
|
84
|
+
records.push(...page.data);
|
|
85
|
+
cursor = page.cursor.next ?? undefined;
|
|
86
|
+
} while (cursor);
|
|
87
|
+
return records;
|
|
88
|
+
};
|
|
89
|
+
const readText = async (path: string, revision: Revision = "final") =>
|
|
90
|
+
new TextDecoder("utf-8", { fatal: true }).decode(
|
|
91
|
+
await evidence.readFile(path, revision),
|
|
92
|
+
);
|
|
93
|
+
return {
|
|
94
|
+
response: { text: evidence.manifest.response?.text ?? "" },
|
|
95
|
+
prompt: evidence.manifest.prompt,
|
|
96
|
+
run: run ? structuredClone(run) : null,
|
|
97
|
+
metrics: structuredClone(metrics),
|
|
98
|
+
recording: {
|
|
99
|
+
evidence: reference,
|
|
100
|
+
sessionID: rootID,
|
|
101
|
+
coverage: evidence.manifest.coverage,
|
|
102
|
+
events: (filter = {}) =>
|
|
103
|
+
structuredClone(
|
|
104
|
+
events.filter(
|
|
105
|
+
({ event }) =>
|
|
106
|
+
(!filter.type || event.type.startsWith(filter.type)) &&
|
|
107
|
+
(!filter.sessionID ||
|
|
108
|
+
("data" in event &&
|
|
109
|
+
(event.data as { sessionID?: string }).sessionID ===
|
|
110
|
+
filter.sessionID)),
|
|
111
|
+
),
|
|
112
|
+
),
|
|
113
|
+
tools: (filter = {}) =>
|
|
114
|
+
structuredClone(
|
|
115
|
+
tools.filter(
|
|
116
|
+
(call) =>
|
|
117
|
+
(!filter.name || call.name === filter.name) &&
|
|
118
|
+
(!filter.sessionID || call.sessionID === filter.sessionID),
|
|
119
|
+
),
|
|
120
|
+
),
|
|
121
|
+
sessions,
|
|
122
|
+
async messages(sessionID) {
|
|
123
|
+
const host = await nativeSDK();
|
|
124
|
+
const ids = sessionID
|
|
125
|
+
? [sessionID]
|
|
126
|
+
: (await sessions()).map((session) => session.id);
|
|
127
|
+
const records: RecordedMessage[] = [];
|
|
128
|
+
for (const id of ids) {
|
|
129
|
+
let cursor: string | undefined;
|
|
130
|
+
do {
|
|
131
|
+
const page = await host.message.list({
|
|
132
|
+
sessionID: id,
|
|
133
|
+
limit: 100,
|
|
134
|
+
...(cursor ? { cursor } : { order: "asc" }),
|
|
135
|
+
});
|
|
136
|
+
records.push(
|
|
137
|
+
...page.data.map((message) => ({ sessionID: id, message })),
|
|
138
|
+
);
|
|
139
|
+
cursor = page.cursor.next ?? undefined;
|
|
140
|
+
} while (cursor);
|
|
141
|
+
}
|
|
142
|
+
return records;
|
|
143
|
+
},
|
|
144
|
+
async export(sessionID = rootID ?? undefined) {
|
|
145
|
+
if (!sessionID) throw new Error("This recording has no native session");
|
|
146
|
+
return (await nativeSDK()).session.export({
|
|
147
|
+
sessionID,
|
|
148
|
+
sanitize: false,
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
workspace: {
|
|
153
|
+
files: (revision = "final") => evidence.files(revision),
|
|
154
|
+
read: (path, revision = "final") => evidence.readFile(path, revision),
|
|
155
|
+
text: readText,
|
|
156
|
+
async diff(path) {
|
|
157
|
+
const initial = evidence
|
|
158
|
+
.files("initial")
|
|
159
|
+
.some((file) => file.path === path);
|
|
160
|
+
const final = evidence
|
|
161
|
+
.files("final")
|
|
162
|
+
.some((file) => file.path === path);
|
|
163
|
+
return {
|
|
164
|
+
initial: initial ? await readText(path, "initial") : null,
|
|
165
|
+
final: final ? await readText(path, "final") : null,
|
|
166
|
+
};
|
|
167
|
+
},
|
|
168
|
+
async materialize(revision = "final") {
|
|
169
|
+
const directory = await mkdtemp(resolve(await temp(), "workspace-"));
|
|
170
|
+
const files = evidence.files(revision);
|
|
171
|
+
for (const file of files.filter((file) => file.symlink === undefined)) {
|
|
172
|
+
const path = contained(directory, file.path);
|
|
173
|
+
await mkdir(dirname(path), { recursive: true });
|
|
174
|
+
await Bun.write(path, await evidence.readFile(file.path, revision));
|
|
175
|
+
}
|
|
176
|
+
for (const file of files.filter((file) => file.symlink !== undefined)) {
|
|
177
|
+
const path = contained(directory, file.path),
|
|
178
|
+
link = file.symlink!;
|
|
179
|
+
const target = link.startsWith("/workspace/")
|
|
180
|
+
? contained(directory, link.slice("/workspace/".length))
|
|
181
|
+
: isAbsolute(link)
|
|
182
|
+
? ""
|
|
183
|
+
: resolve(dirname(path), link);
|
|
184
|
+
if (
|
|
185
|
+
!target ||
|
|
186
|
+
(target !== directory && !target.startsWith(directory + sep))
|
|
187
|
+
)
|
|
188
|
+
throw new Error(
|
|
189
|
+
`Recorded symlink leaves its workspace: ${file.path}`,
|
|
190
|
+
);
|
|
191
|
+
await mkdir(dirname(path), { recursive: true });
|
|
192
|
+
const isDirectory = files.some((item) =>
|
|
193
|
+
item.path.startsWith(
|
|
194
|
+
relative(directory, target).replaceAll("\\", "/") + "/",
|
|
195
|
+
),
|
|
196
|
+
);
|
|
197
|
+
await symlink(
|
|
198
|
+
process.platform === "win32" && isDirectory
|
|
199
|
+
? target
|
|
200
|
+
: relative(dirname(path), target),
|
|
201
|
+
path,
|
|
202
|
+
process.platform === "win32" && isDirectory
|
|
203
|
+
? "junction"
|
|
204
|
+
: isDirectory
|
|
205
|
+
? "dir"
|
|
206
|
+
: "file",
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
return directory;
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
native: {
|
|
213
|
+
version: archive?.opencodeVersion ?? null,
|
|
214
|
+
readerVersion: OPENCODE_VERSION,
|
|
215
|
+
database: () =>
|
|
216
|
+
(database ??= (async () => {
|
|
217
|
+
const db = new Database(await copyDatabase("readonly.db"), {
|
|
218
|
+
readonly: true,
|
|
219
|
+
});
|
|
220
|
+
db.exec("PRAGMA query_only=ON");
|
|
221
|
+
return db;
|
|
222
|
+
})()),
|
|
223
|
+
sdk: nativeSDK,
|
|
224
|
+
schema: () => import("@opencode/schema"),
|
|
225
|
+
},
|
|
226
|
+
async [Symbol.asyncDispose]() {
|
|
227
|
+
try {
|
|
228
|
+
if (sdk) await (await sdk).close();
|
|
229
|
+
} finally {
|
|
230
|
+
try {
|
|
231
|
+
if (database) (await database).close();
|
|
232
|
+
} finally {
|
|
233
|
+
if (temporary)
|
|
234
|
+
await rm(await temporary, {
|
|
235
|
+
recursive: true,
|
|
236
|
+
force: true,
|
|
237
|
+
maxRetries: 5,
|
|
238
|
+
retryDelay: 100,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { EvidenceRef, ToolCall } from "../../types";
|
|
2
|
+
import type {
|
|
3
|
+
RecordedEvent,
|
|
4
|
+
RunMetrics,
|
|
5
|
+
ToolMetrics,
|
|
6
|
+
} from "../../judge-context";
|
|
7
|
+
import { mergeRuntime, runtimeMs, type RuntimeInterval } from "../../runtime";
|
|
8
|
+
|
|
9
|
+
const toolsSummary = (tools: ToolCall[]): ToolMetrics => {
|
|
10
|
+
const succeeded = tools.filter((call) => call.status === "succeeded").length;
|
|
11
|
+
const failed = tools.filter((call) => call.status === "failed").length;
|
|
12
|
+
return {
|
|
13
|
+
calls: tools.length,
|
|
14
|
+
succeeded,
|
|
15
|
+
failed,
|
|
16
|
+
unfinished: tools.length - succeeded - failed,
|
|
17
|
+
errorRate: succeeded + failed ? failed / (succeeded + failed) : null,
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** Measurements use deduplicated native records, never scored criterion values. */
|
|
22
|
+
export function measureRecording(
|
|
23
|
+
supplied: RecordedEvent[],
|
|
24
|
+
tools: ToolCall[],
|
|
25
|
+
evidence: EvidenceRef,
|
|
26
|
+
): RunMetrics {
|
|
27
|
+
const events = [
|
|
28
|
+
...new Map(
|
|
29
|
+
supplied.map((item) => [
|
|
30
|
+
"id" in item.event ? item.event.id : String(item.sequence),
|
|
31
|
+
item.event,
|
|
32
|
+
]),
|
|
33
|
+
).values(),
|
|
34
|
+
].sort(
|
|
35
|
+
(a, b) =>
|
|
36
|
+
("created" in a ? a.created : 0) - ("created" in b ? b.created : 0),
|
|
37
|
+
);
|
|
38
|
+
const count = (type: string) =>
|
|
39
|
+
events.filter((event) => event.type === type).length;
|
|
40
|
+
const tokens = {
|
|
41
|
+
input: 0,
|
|
42
|
+
output: 0,
|
|
43
|
+
reasoning: 0,
|
|
44
|
+
cache: { read: 0, write: 0 },
|
|
45
|
+
};
|
|
46
|
+
let reportedUSD = 0,
|
|
47
|
+
usageRecords = 0,
|
|
48
|
+
usageComplete = true,
|
|
49
|
+
costComplete = true;
|
|
50
|
+
const intervals: RuntimeInterval[] = [],
|
|
51
|
+
starts = new Map<string, number>();
|
|
52
|
+
const executions: RuntimeInterval[] = [],
|
|
53
|
+
executionStarts = new Map<string, number>();
|
|
54
|
+
const created = new Set<string>();
|
|
55
|
+
const sessions = new Set<string>(),
|
|
56
|
+
sequences = new Map<string, number[]>();
|
|
57
|
+
for (const event of events) {
|
|
58
|
+
if (!("data" in event)) continue;
|
|
59
|
+
const data = event.data as Record<string, unknown>;
|
|
60
|
+
if (typeof data.sessionID === "string") sessions.add(data.sessionID);
|
|
61
|
+
if (event.type === "session.created") created.add(event.data.sessionID);
|
|
62
|
+
if (event.type === "session.execution.started")
|
|
63
|
+
executionStarts.set(event.data.sessionID, event.created);
|
|
64
|
+
if (
|
|
65
|
+
[
|
|
66
|
+
"session.execution.succeeded",
|
|
67
|
+
"session.execution.failed",
|
|
68
|
+
"session.execution.interrupted",
|
|
69
|
+
].includes(event.type) &&
|
|
70
|
+
typeof data.sessionID === "string" &&
|
|
71
|
+
"created" in event
|
|
72
|
+
) {
|
|
73
|
+
const start = executionStarts.get(data.sessionID);
|
|
74
|
+
if (start !== undefined) executions.push({ start, end: event.created });
|
|
75
|
+
executionStarts.delete(data.sessionID);
|
|
76
|
+
}
|
|
77
|
+
if ("durable" in event) {
|
|
78
|
+
const values = sequences.get(event.durable.aggregateID) ?? [];
|
|
79
|
+
values.push(event.durable.seq);
|
|
80
|
+
sequences.set(event.durable.aggregateID, values);
|
|
81
|
+
}
|
|
82
|
+
const key = `${data.sessionID}:${data.assistantMessageID ?? "compaction"}`;
|
|
83
|
+
if (
|
|
84
|
+
event.type === "session.step.started" ||
|
|
85
|
+
event.type === "session.compaction.started"
|
|
86
|
+
)
|
|
87
|
+
starts.set(key, event.created);
|
|
88
|
+
if (
|
|
89
|
+
[
|
|
90
|
+
"session.step.ended",
|
|
91
|
+
"session.step.failed",
|
|
92
|
+
"session.compaction.ended",
|
|
93
|
+
"session.compaction.failed",
|
|
94
|
+
].includes(event.type)
|
|
95
|
+
) {
|
|
96
|
+
const start = starts.get(key);
|
|
97
|
+
if (start !== undefined && "created" in event)
|
|
98
|
+
intervals.push({ start, end: event.created });
|
|
99
|
+
starts.delete(key);
|
|
100
|
+
}
|
|
101
|
+
if (
|
|
102
|
+
![
|
|
103
|
+
"session.step.ended",
|
|
104
|
+
"session.step.failed",
|
|
105
|
+
"session.usage.recorded",
|
|
106
|
+
].includes(event.type)
|
|
107
|
+
)
|
|
108
|
+
continue;
|
|
109
|
+
usageRecords++;
|
|
110
|
+
if (typeof data.cost === "number" && Number.isFinite(data.cost))
|
|
111
|
+
reportedUSD += data.cost;
|
|
112
|
+
else costComplete = false;
|
|
113
|
+
const usage = data.tokens as typeof tokens | undefined;
|
|
114
|
+
if (
|
|
115
|
+
!usage ||
|
|
116
|
+
![
|
|
117
|
+
usage.input,
|
|
118
|
+
usage.output,
|
|
119
|
+
usage.reasoning,
|
|
120
|
+
usage.cache?.read,
|
|
121
|
+
usage.cache?.write,
|
|
122
|
+
].every(Number.isFinite)
|
|
123
|
+
) {
|
|
124
|
+
usageComplete = false;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
tokens.input += usage.input;
|
|
128
|
+
tokens.output += usage.output;
|
|
129
|
+
tokens.reasoning += usage.reasoning;
|
|
130
|
+
tokens.cache.read += usage.cache.read;
|
|
131
|
+
tokens.cache.write += usage.cache.write;
|
|
132
|
+
}
|
|
133
|
+
const measuredTokens = usageRecords && usageComplete ? tokens : null;
|
|
134
|
+
const cost = usageRecords && costComplete ? reportedUSD : null;
|
|
135
|
+
const clock = events.reduce(
|
|
136
|
+
(latest, event) =>
|
|
137
|
+
"created" in event ? Math.max(latest, event.created) : latest,
|
|
138
|
+
0,
|
|
139
|
+
);
|
|
140
|
+
const modelActiveMs = runtimeMs(mergeRuntime(intervals), clock);
|
|
141
|
+
const complete =
|
|
142
|
+
sequences.size > 0 &&
|
|
143
|
+
starts.size === 0 &&
|
|
144
|
+
executionStarts.size === 0 &&
|
|
145
|
+
[...sessions].every((id) => created.has(id)) &&
|
|
146
|
+
[...sequences.values()].every((values) => {
|
|
147
|
+
const sorted = [...new Set(values)].sort((a, b) => a - b);
|
|
148
|
+
return sorted.every(
|
|
149
|
+
(value, index) => index === 0 || value === sorted[index - 1] + 1,
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
return {
|
|
153
|
+
version: 1,
|
|
154
|
+
scope: "candidate",
|
|
155
|
+
complete,
|
|
156
|
+
sessions: sessions.size,
|
|
157
|
+
requests: {
|
|
158
|
+
started: count("session.step.started"),
|
|
159
|
+
succeeded: count("session.step.ended"),
|
|
160
|
+
failed: count("session.step.failed"),
|
|
161
|
+
retries: count("session.retry.scheduled"),
|
|
162
|
+
},
|
|
163
|
+
tokens: measuredTokens,
|
|
164
|
+
cost: { usd: cost, reportedUSD, complete: cost !== null },
|
|
165
|
+
tools: {
|
|
166
|
+
...toolsSummary(tools),
|
|
167
|
+
byName: Object.fromEntries(
|
|
168
|
+
[...new Set(tools.map((call) => call.name))]
|
|
169
|
+
.sort()
|
|
170
|
+
.map((name) => [
|
|
171
|
+
name,
|
|
172
|
+
toolsSummary(tools.filter((call) => call.name === name)),
|
|
173
|
+
]),
|
|
174
|
+
),
|
|
175
|
+
},
|
|
176
|
+
compactions: {
|
|
177
|
+
started: count("session.compaction.started"),
|
|
178
|
+
completed: count("session.compaction.ended"),
|
|
179
|
+
failed: count("session.compaction.failed"),
|
|
180
|
+
},
|
|
181
|
+
timing: {
|
|
182
|
+
elapsedMs: executions.length
|
|
183
|
+
? runtimeMs(mergeRuntime(executions), clock)
|
|
184
|
+
: null,
|
|
185
|
+
modelActiveMs,
|
|
186
|
+
outputTokensPerSecond:
|
|
187
|
+
measuredTokens && modelActiveMs > 0
|
|
188
|
+
? (measuredTokens.output * 1000) / modelActiveMs
|
|
189
|
+
: null,
|
|
190
|
+
},
|
|
191
|
+
evidence,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
@@ -5,7 +5,7 @@ import { mkdirSync, existsSync } from "node:fs";
|
|
|
5
5
|
import { dirname, resolve } from "node:path";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import * as tables from "./schema";
|
|
8
|
-
import { isScored, JUDGE_PROTOCOL,
|
|
8
|
+
import { isScored, JUDGE_PROTOCOL, criterionMean } from "../../judgment";
|
|
9
9
|
import type {
|
|
10
10
|
BenchmarkRun,
|
|
11
11
|
EvalRun,
|
|
@@ -19,7 +19,7 @@ import type {
|
|
|
19
19
|
} from "../../types";
|
|
20
20
|
|
|
21
21
|
const APPLICATION_ID = 0x4f455632;
|
|
22
|
-
const SCHEMA_VERSION =
|
|
22
|
+
const SCHEMA_VERSION = 5;
|
|
23
23
|
const timestamp = () => new Date().toISOString();
|
|
24
24
|
|
|
25
25
|
/** One owner for run selection and append-only finalized execution records. */
|
|
@@ -257,13 +257,16 @@ export class Results {
|
|
|
257
257
|
}
|
|
258
258
|
startJudge(input: JudgeRunInput) {
|
|
259
259
|
if (
|
|
260
|
-
!input.
|
|
261
|
-
|
|
260
|
+
(!input.code &&
|
|
261
|
+
(!input.agent || !input.model || !input.criteria.length)) ||
|
|
262
|
+
(!!input.rubric &&
|
|
263
|
+
(!input.agent || !input.model || !input.criteria.length)) ||
|
|
264
|
+
!["llm", "code", "hybrid"].includes(input.kind) ||
|
|
262
265
|
!input.runtimeHash ||
|
|
263
266
|
input.protocol !== JUDGE_PROTOCOL
|
|
264
267
|
)
|
|
265
268
|
throw new Error(
|
|
266
|
-
"Judge inputs must
|
|
269
|
+
"Judge inputs must include their code or LLM configuration, criteria, runtime, and protocol",
|
|
267
270
|
);
|
|
268
271
|
const value: JudgeRun = {
|
|
269
272
|
id: `judge_${randomUUID()}`,
|
|
@@ -301,21 +304,28 @@ export class Results {
|
|
|
301
304
|
const saved = this.judgeRun(value.id);
|
|
302
305
|
if (!saved || saved.state !== "running")
|
|
303
306
|
throw new Error("Judge run is not running");
|
|
304
|
-
if (
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
307
|
+
if (value.state === "completed") {
|
|
308
|
+
const expected = [
|
|
309
|
+
...value.input.criteria.map((criterion) => criterion.id),
|
|
310
|
+
...Object.keys(value.code?.scores ?? {}),
|
|
311
|
+
];
|
|
312
|
+
const scores = value.judgment?.scores;
|
|
313
|
+
if (
|
|
314
|
+
!scores ||
|
|
315
|
+
Array.isArray(scores) ||
|
|
316
|
+
new Set(expected).size !== expected.length ||
|
|
317
|
+
Object.keys(scores).length !== expected.length ||
|
|
318
|
+
expected.some((id) => !Object.hasOwn(scores, id)) ||
|
|
319
|
+
Object.values(scores).some(
|
|
320
|
+
(score) => score.value !== null && !isScored(score.value),
|
|
314
321
|
) ||
|
|
315
|
-
value.judgment
|
|
316
|
-
(value.
|
|
317
|
-
|
|
318
|
-
|
|
322
|
+
value.judgment!.value !== criterionMean(scores) ||
|
|
323
|
+
(value.input.code && value.code?.state !== "completed")
|
|
324
|
+
)
|
|
325
|
+
throw new Error(
|
|
326
|
+
"A completed judge run requires a valid, complete judgment",
|
|
327
|
+
);
|
|
328
|
+
}
|
|
319
329
|
if (
|
|
320
330
|
value.state === "completed" &&
|
|
321
331
|
this.evalRun(value.input.evalRunId)?.evidence?.hash !==
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
|
+
import type { OpenCode } from "@opencode/sdk";
|
|
3
|
+
import type {
|
|
4
|
+
SessionInfo,
|
|
5
|
+
SessionMessageInfo,
|
|
6
|
+
SessionTransferData,
|
|
7
|
+
} from "@opencode/client";
|
|
8
|
+
import type {
|
|
9
|
+
EvalRun,
|
|
10
|
+
EvidenceRef,
|
|
11
|
+
OpenCodeStreamEvent,
|
|
12
|
+
ToolCall,
|
|
13
|
+
Cost,
|
|
14
|
+
Accounting,
|
|
15
|
+
} from "./types";
|
|
16
|
+
|
|
17
|
+
export type JsonValue =
|
|
18
|
+
| null
|
|
19
|
+
| boolean
|
|
20
|
+
| number
|
|
21
|
+
| string
|
|
22
|
+
| JsonValue[]
|
|
23
|
+
| { [key: string]: JsonValue };
|
|
24
|
+
export type ScoreValue = boolean | number | null;
|
|
25
|
+
|
|
26
|
+
/** Code judges are ordinary functions. Only an optional scores map has grading semantics. */
|
|
27
|
+
export type JudgeFunction = (
|
|
28
|
+
context: JudgeContext,
|
|
29
|
+
) => JsonValue | Promise<JsonValue>;
|
|
30
|
+
|
|
31
|
+
export type ToolMetrics = {
|
|
32
|
+
calls: number;
|
|
33
|
+
succeeded: number;
|
|
34
|
+
failed: number;
|
|
35
|
+
unfinished: number;
|
|
36
|
+
/** Failed / (succeeded + failed); null when there are no terminal calls. */
|
|
37
|
+
errorRate: number | null;
|
|
38
|
+
};
|
|
39
|
+
export type RunMetrics = {
|
|
40
|
+
version: 1;
|
|
41
|
+
scope: "candidate";
|
|
42
|
+
/** False for constructed controls or incomplete durable execution history. */
|
|
43
|
+
complete: boolean;
|
|
44
|
+
sessions: number;
|
|
45
|
+
requests: {
|
|
46
|
+
started: number;
|
|
47
|
+
succeeded: number;
|
|
48
|
+
failed: number;
|
|
49
|
+
retries: number;
|
|
50
|
+
};
|
|
51
|
+
tokens: Accounting["tokens"] | null;
|
|
52
|
+
cost: Cost;
|
|
53
|
+
tools: ToolMetrics & { byName: Record<string, ToolMetrics> };
|
|
54
|
+
compactions: { started: number; completed: number; failed: number };
|
|
55
|
+
timing: {
|
|
56
|
+
elapsedMs: number | null;
|
|
57
|
+
modelActiveMs: number;
|
|
58
|
+
/** Output tokens / model-active seconds; overlapping requests count once. */
|
|
59
|
+
outputTokensPerSecond: number | null;
|
|
60
|
+
};
|
|
61
|
+
evidence: EvidenceRef;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type RecordedEvent = {
|
|
65
|
+
sequence: number;
|
|
66
|
+
time: string;
|
|
67
|
+
event: OpenCodeStreamEvent;
|
|
68
|
+
};
|
|
69
|
+
export type RecordedMessage = {
|
|
70
|
+
sessionID: string;
|
|
71
|
+
message: SessionMessageInfo;
|
|
72
|
+
};
|
|
73
|
+
export type RecordedFile = {
|
|
74
|
+
path: string;
|
|
75
|
+
bytes: number;
|
|
76
|
+
sha256?: string;
|
|
77
|
+
symlink?: string;
|
|
78
|
+
};
|
|
79
|
+
export type Revision = "initial" | "final";
|
|
80
|
+
|
|
81
|
+
export interface JudgeContext {
|
|
82
|
+
/** Missing response text is normalized to an empty string. */
|
|
83
|
+
readonly response: { readonly text: string };
|
|
84
|
+
readonly prompt: string;
|
|
85
|
+
/** Null for constructed evidence that did not execute a candidate. */
|
|
86
|
+
readonly run: Readonly<EvalRun> | null;
|
|
87
|
+
readonly metrics: Readonly<RunMetrics>;
|
|
88
|
+
readonly recording: {
|
|
89
|
+
readonly evidence: EvidenceRef;
|
|
90
|
+
readonly sessionID: string | null;
|
|
91
|
+
readonly coverage: "recorded-session" | "recorded-prefix";
|
|
92
|
+
events(filter?: { sessionID?: string; type?: string }): RecordedEvent[];
|
|
93
|
+
tools(filter?: { sessionID?: string; name?: string }): ToolCall[];
|
|
94
|
+
sessions(): Promise<SessionInfo[]>;
|
|
95
|
+
/** Full projected history, including messages before compaction. */
|
|
96
|
+
messages(sessionID?: string): Promise<RecordedMessage[]>;
|
|
97
|
+
export(sessionID?: string): Promise<SessionTransferData>;
|
|
98
|
+
};
|
|
99
|
+
readonly workspace: {
|
|
100
|
+
files(revision?: Revision): RecordedFile[];
|
|
101
|
+
read(path: string, revision?: Revision): Promise<Uint8Array>;
|
|
102
|
+
text(path: string, revision?: Revision): Promise<string>;
|
|
103
|
+
diff(
|
|
104
|
+
path: string,
|
|
105
|
+
): Promise<{ initial: string | null; final: string | null }>;
|
|
106
|
+
/** Restore a disposable snapshot for author-owned verification commands. */
|
|
107
|
+
materialize(revision?: Revision): Promise<string>;
|
|
108
|
+
};
|
|
109
|
+
readonly native: {
|
|
110
|
+
readonly version: string | null;
|
|
111
|
+
readonly readerVersion: string;
|
|
112
|
+
/** Read-only SQL access to a verified, disposable native database copy. */
|
|
113
|
+
database(): Promise<Database>;
|
|
114
|
+
/** The pinned OpenCode API/SDK over a separate disposable database copy. */
|
|
115
|
+
sdk(): Promise<OpenCode.Interface>;
|
|
116
|
+
schema(): Promise<typeof import("@opencode/schema")>;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Frozen judging source and dependency identity, kept in host-side run records. */
|
|
121
|
+
export type CodeJudgeDefinition = {
|
|
122
|
+
file: string;
|
|
123
|
+
hash: string;
|
|
124
|
+
source: string;
|
|
125
|
+
sourceMap: string;
|
|
126
|
+
dependencies: Record<string, string>;
|
|
127
|
+
};
|
|
128
|
+
export type CodeJudgeExecution = {
|
|
129
|
+
state: "completed" | "failed" | "timed_out";
|
|
130
|
+
startedAt: string;
|
|
131
|
+
completedAt: string;
|
|
132
|
+
elapsedMs: number;
|
|
133
|
+
/** Original JSON, before boolean score normalization. */
|
|
134
|
+
output?: JsonValue;
|
|
135
|
+
scores?: Record<string, number | null>;
|
|
136
|
+
metrics?: RunMetrics;
|
|
137
|
+
error?: string;
|
|
138
|
+
sourceHash: string;
|
|
139
|
+
stdout: string;
|
|
140
|
+
stderr: string;
|
|
141
|
+
};
|