@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
package/src/cli.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
loadBenchmark,
|
|
7
7
|
buildImage,
|
|
8
8
|
addModels,
|
|
9
|
+
removeModels,
|
|
9
10
|
retryEvalRun,
|
|
10
11
|
judgeRun,
|
|
11
12
|
serveResults,
|
|
@@ -39,6 +40,7 @@ Commands:
|
|
|
39
40
|
snapshot <run> <name> Export a score snapshot
|
|
40
41
|
merge-runs <to> <from> Merge results into one aggregate
|
|
41
42
|
add-models <run> Add models with repeated --model flags
|
|
43
|
+
remove-models <run> Remove active models while retaining their evidence
|
|
42
44
|
retry <run> <eval-run> Retry a candidate execution
|
|
43
45
|
rejudge <run> <eval-run> Judge the saved evidence again
|
|
44
46
|
|
|
@@ -46,7 +48,7 @@ Options:
|
|
|
46
48
|
--benchmark <dir> Benchmark directory (default: current directory)
|
|
47
49
|
--run <dir> Resume a specific result directory
|
|
48
50
|
--new Start a new result
|
|
49
|
-
--model <provider/id>
|
|
51
|
+
--model <provider/id> Model to add or remove (repeatable)
|
|
50
52
|
--only-model <ref> Execute only this model (repeatable)
|
|
51
53
|
--only-eval <id> Execute only this eval (repeatable)
|
|
52
54
|
--only-repetition <n> Execute only this repetition (repeatable)
|
|
@@ -125,6 +127,16 @@ Requires Bun 1.4.2+, Docker, and an authenticated OpenCode installation.`);
|
|
|
125
127
|
if (!args[1])
|
|
126
128
|
throw new Error("add-models requires a benchmark run directory");
|
|
127
129
|
console.log((await addModels(resolve(args[1]), models())).directory);
|
|
130
|
+
} else if (command === "remove-models") {
|
|
131
|
+
if (!args[1])
|
|
132
|
+
throw new Error("remove-models requires a benchmark run directory");
|
|
133
|
+
const result = await removeModels(resolve(args[1]), models());
|
|
134
|
+
console.log(JSON.stringify({
|
|
135
|
+
directory: result.directory,
|
|
136
|
+
removed: result.removed,
|
|
137
|
+
retiredSlots: result.retiredSlots,
|
|
138
|
+
status: result.benchmark.state,
|
|
139
|
+
}, null, 2));
|
|
128
140
|
} else if (command === "retry" || command === "rejudge") {
|
|
129
141
|
if (!args[1] || !args[2])
|
|
130
142
|
throw new Error(
|
|
@@ -137,7 +149,7 @@ Requires Bun 1.4.2+, Docker, and an authenticated OpenCode installation.`);
|
|
|
137
149
|
console.log(JSON.stringify(result, null, 2));
|
|
138
150
|
} else
|
|
139
151
|
throw new Error(
|
|
140
|
-
"Commands: image, plan, run, view, add-models, merge-runs, retry, rejudge, snapshot",
|
|
152
|
+
"Commands: image, plan, run, view, add-models, remove-models, merge-runs, retry, rejudge, snapshot",
|
|
141
153
|
);
|
|
142
154
|
} catch (error) {
|
|
143
155
|
console.error(error instanceof Error ? error.message : String(error));
|
package/src/evidence.ts
CHANGED
|
@@ -11,7 +11,10 @@ export type EvidenceQuery = {
|
|
|
11
11
|
| "tool"
|
|
12
12
|
| "artifacts"
|
|
13
13
|
| "artifact"
|
|
14
|
-
| "diff"
|
|
14
|
+
| "diff"
|
|
15
|
+
| "metrics";
|
|
16
|
+
/** A dot-separated measurement path for the metrics query. */
|
|
17
|
+
metric?: string;
|
|
15
18
|
sessionID?: string;
|
|
16
19
|
type?: string;
|
|
17
20
|
id?: string;
|
package/src/index.ts
CHANGED
|
@@ -8,8 +8,8 @@ export type {
|
|
|
8
8
|
EvalRun,
|
|
9
9
|
JudgeRun,
|
|
10
10
|
Judgment,
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
CriterionDefinition,
|
|
12
|
+
CriterionScore,
|
|
13
13
|
EvidenceCitation,
|
|
14
14
|
ToolCall,
|
|
15
15
|
MonitorPolicy,
|
|
@@ -30,7 +30,18 @@ export type {
|
|
|
30
30
|
JudgeSession,
|
|
31
31
|
} from "./evidence";
|
|
32
32
|
export { CANDIDATE_TIMEOUT_MS } from "./types";
|
|
33
|
-
export {
|
|
33
|
+
export { rubricCriteria, criterionMean, isScored } from "./judgment";
|
|
34
|
+
export type {
|
|
35
|
+
JudgeContext,
|
|
36
|
+
JudgeFunction,
|
|
37
|
+
JsonValue,
|
|
38
|
+
ScoreValue,
|
|
39
|
+
RunMetrics,
|
|
40
|
+
ToolMetrics,
|
|
41
|
+
RecordedEvent,
|
|
42
|
+
RecordedMessage,
|
|
43
|
+
RecordedFile,
|
|
44
|
+
} from "./judge-context";
|
|
34
45
|
export { loadBenchmark } from "./app/load-benchmark";
|
|
35
46
|
export {
|
|
36
47
|
runBenchmark,
|
|
@@ -38,6 +49,7 @@ export {
|
|
|
38
49
|
type RunBenchmarkOptions,
|
|
39
50
|
} from "./app/run-benchmark";
|
|
40
51
|
export { addModels } from "./app/add-models";
|
|
52
|
+
export { removeModels } from "./app/remove-models";
|
|
41
53
|
export type { CostEstimate } from "./app/cost-plan";
|
|
42
54
|
export { mergeBenchmarkRuns } from "./app/merge-runs";
|
|
43
55
|
export { retryEvalRun } from "./app/retry-run";
|
|
@@ -46,6 +58,7 @@ export { judgeEvidence, recordEvidence } from "./app/judge-evidence";
|
|
|
46
58
|
export {
|
|
47
59
|
readBenchmarkRun,
|
|
48
60
|
readEvidence,
|
|
61
|
+
readRecording,
|
|
49
62
|
verifyJudgmentEvidence,
|
|
50
63
|
} from "./app/read-run";
|
|
51
64
|
export { snapshotBenchmarkRun } from "./app/snapshot-run";
|
|
@@ -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
|
|
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(
|
|
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
|
|
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
|
|
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
|
+
}
|