reeval 0.9.0 → 0.10.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/README.md +42 -30
- package/THIRD_PARTY_NOTICES.md +218 -11
- package/assets/package.json +1 -1
- package/assets/pnpm-lock.yaml +30 -30
- package/dist/adapter-DWlpOtRn.js +279 -0
- package/dist/adapter-DWlpOtRn.js.map +1 -0
- package/dist/{agent-6FefIhX7.js → agent-CURAwx_q.js} +6 -6
- package/dist/{agent-6FefIhX7.js.map → agent-CURAwx_q.js.map} +1 -1
- package/dist/{agent-WSmTaesf.js → agent-CaS_2Sce.js} +46 -15
- package/dist/agent-CaS_2Sce.js.map +1 -0
- package/dist/{analysis-DlGBDjTj.js → analysis-Bxub9LMT.js} +4 -4
- package/dist/{analysis-DlGBDjTj.js.map → analysis-Bxub9LMT.js.map} +1 -1
- package/dist/{assemble-BCZzTyR7.js → assemble-B9Alds-z.js} +69 -41
- package/dist/assemble-B9Alds-z.js.map +1 -0
- package/dist/assemble-J5MukZFY.js +1 -0
- package/dist/{client-Bbo9SPlK.js → assert-EUIoRdIt.js} +10 -113
- package/dist/assert-EUIoRdIt.js.map +1 -0
- package/dist/bridge/index.mjs +248 -55
- package/dist/{capture-Bp74Z6Fr.js → capture-CNb9SuU5.js} +145 -31
- package/dist/capture-CNb9SuU5.js.map +1 -0
- package/dist/capture-jquXZBKK.js +1 -0
- package/dist/{cli-CVrMh_qz.js → cli-D303Vj5N.js} +133 -115
- package/dist/cli-D303Vj5N.js.map +1 -0
- package/dist/{cli-spec-CKEYa8V2.js → cli-spec-BPZbQG2p.js} +36 -17
- package/dist/cli-spec-BPZbQG2p.js.map +1 -0
- package/dist/cli.js +4 -2
- package/dist/cli.js.map +1 -1
- package/dist/client-fcwgwOAD.js +106 -0
- package/dist/client-fcwgwOAD.js.map +1 -0
- package/dist/{command-CzIFp1JQ.js → command-BLfT7vnQ.js} +18 -17
- package/dist/command-BLfT7vnQ.js.map +1 -0
- package/dist/{command-C5xFPKHM.js → command-BT9a37ZR.js} +374 -72
- package/dist/command-BT9a37ZR.js.map +1 -0
- package/dist/{command-DUT5l13I.js → command-Bge8F7Ko.js} +316 -83
- package/dist/command-Bge8F7Ko.js.map +1 -0
- package/dist/command-BkFjijN2.js +1 -0
- package/dist/{command-BFP0hKEb.js → command-CMJtHKRa.js} +14 -14
- package/dist/{command-BFP0hKEb.js.map → command-CMJtHKRa.js.map} +1 -1
- package/dist/commands-1p-bIWhN.js +1 -0
- package/dist/{commands-CLmPBrTS.js → commands-BFx16WB8.js} +7 -7
- package/dist/commands-BFx16WB8.js.map +1 -0
- package/dist/config-DEgnUiU-.js +756 -0
- package/dist/config-DEgnUiU-.js.map +1 -0
- package/dist/{context-DbSMEVKs.js → context-C1mxXh80.js} +11 -3
- package/dist/context-C1mxXh80.js.map +1 -0
- package/dist/{db-CfQFwJtq.js → db-CdHizc4_.js} +455 -36
- package/dist/db-CdHizc4_.js.map +1 -0
- package/dist/db-Dm1cRJJP.js +2 -0
- package/dist/debug-D7bEv_UV.js +1 -0
- package/dist/{dist-CTzThfu_.js → dist-B8hJImYt.js} +17 -4
- package/dist/dist-B8hJImYt.js.map +1 -0
- package/dist/{docker-CRQGQbIt.js → docker-7b_2SaH7.js} +22 -21
- package/dist/docker-7b_2SaH7.js.map +1 -0
- package/dist/{docker-DV2GNgle.js → docker-sZrNrr82.js} +3 -3
- package/dist/docker-sZrNrr82.js.map +1 -0
- package/dist/docker-yauK-9X8.js +1 -0
- package/dist/{doctor-DlKycx5H.js → doctor-BYNok4UO.js} +262 -83
- package/dist/doctor-BYNok4UO.js.map +1 -0
- package/dist/{duration-JsJrIWXq.js → duration-CEOQww97.js} +2 -2
- package/dist/{duration-JsJrIWXq.js.map → duration-CEOQww97.js.map} +1 -1
- package/dist/{egress-BhzNh7Yi.js → egress-C4PZTSSe.js} +146 -14
- package/dist/egress-C4PZTSSe.js.map +1 -0
- package/dist/environment-DNjRk0nU.js +1133 -0
- package/dist/environment-DNjRk0nU.js.map +1 -0
- package/dist/{errors-yDAEwmZo.js → errors-B6BSMs03.js} +2 -2
- package/dist/errors-B6BSMs03.js.map +1 -0
- package/dist/{errors-C9w_ud7g.js → errors-r025B0Zb.js} +4 -4
- package/dist/errors-r025B0Zb.js.map +1 -0
- package/dist/evaluate-C1UVL21l.d.ts +92 -0
- package/dist/evaluate-D4zeRGBF.js +2351 -0
- package/dist/evaluate-D4zeRGBF.js.map +1 -0
- package/dist/files-BAt0i00X.js +1 -0
- package/dist/{files-CSzVADHn.js → files-BXUdY8tr.js} +9 -8
- package/dist/{files-CSzVADHn.js.map → files-BXUdY8tr.js.map} +1 -1
- package/dist/files-BhyltlDB.js +1 -0
- package/dist/files-Bk1mC3AN.js +43 -0
- package/dist/files-Bk1mC3AN.js.map +1 -0
- package/dist/{files-DJUFg-4e.js → files-DQmHE7pb.js} +5 -5
- package/dist/{files-DJUFg-4e.js.map → files-DQmHE7pb.js.map} +1 -1
- package/dist/files-ZZPqfAo6.js +1 -0
- package/dist/harness-B8QC9u0O.js +323 -0
- package/dist/harness-B8QC9u0O.js.map +1 -0
- package/dist/harness.d.ts +16 -30
- package/dist/harness.js +1 -2
- package/dist/{header-CALhmafp.js → header-hK-Z_Po9.js} +2 -2
- package/dist/{header-CALhmafp.js.map → header-hK-Z_Po9.js.map} +1 -1
- package/dist/{images-BMgdpMAX.js → images-0On0cztu.js} +114 -45
- package/dist/images-0On0cztu.js.map +1 -0
- package/dist/index.d.ts +9 -37
- package/dist/index.js +6 -2138
- package/dist/{init-BIdLF0F9.js → init-B77Rpcqc.js} +26 -8
- package/dist/init-B77Rpcqc.js.map +1 -0
- package/dist/init-BrOWin3W.js +1 -0
- package/dist/{invocations-CLaL8AuW.js → invocations-DbGfBAA_.js} +4 -4
- package/dist/{invocations-CLaL8AuW.js.map → invocations-DbGfBAA_.js.map} +1 -1
- package/dist/{judge-CeYPN8P_.js → judge-DW_GDOmB.js} +11 -19
- package/dist/{judge-CeYPN8P_.js.map → judge-DW_GDOmB.js.map} +1 -1
- package/dist/judge.d.ts +2 -2
- package/dist/judge.js +1 -1
- package/dist/{live-S0qODb6o.js → live-jlfszAbb.js} +9 -20
- package/dist/live-jlfszAbb.js.map +1 -0
- package/dist/matchers-Ddmc2z7H.js +118 -0
- package/dist/matchers-Ddmc2z7H.js.map +1 -0
- package/dist/{model-DEQo3FkZ.js → model-DaVCY3rb.js} +2 -2
- package/dist/{model-DEQo3FkZ.js.map → model-DaVCY3rb.js.map} +1 -1
- package/dist/model-NM3ID0uR.js +1 -0
- package/dist/{model-B1BZoZ9b.js → model-h_cDtcRr.js} +18 -17
- package/dist/model-h_cDtcRr.js.map +1 -0
- package/dist/model-nwWJvaUR.js +1 -0
- package/dist/{model-BOnlAPzs.js → model-oqcDplJG.js} +111 -19
- package/dist/model-oqcDplJG.js.map +1 -0
- package/dist/{node-Co7EXge0.js → node-B3ydHo6t.js} +10 -47
- package/dist/node-B3ydHo6t.js.map +1 -0
- package/dist/object-CLf2-tNq.js +9 -0
- package/dist/object-CLf2-tNq.js.map +1 -0
- package/dist/{opencode-providers-ByD85Q0l.js → opencode-providers-J9colP0-.js} +96 -21
- package/dist/opencode-providers-J9colP0-.js.map +1 -0
- package/dist/options-Bh2W63dK.js +1119 -0
- package/dist/options-Bh2W63dK.js.map +1 -0
- package/dist/{pairs-DDsjgm5F.js → pairs-CnInRu-G.js} +76 -2
- package/dist/{pairs-DDsjgm5F.js.map → pairs-CnInRu-G.js.map} +1 -1
- package/dist/{patch-bDV8YFW0.js → patch-Zac5aIRM.js} +6 -6
- package/dist/{patch-bDV8YFW0.js.map → patch-Zac5aIRM.js.map} +1 -1
- package/dist/{plan-DUydFc_1.js → plan-C9Cy27Ay.js} +24 -9
- package/dist/plan-C9Cy27Ay.js.map +1 -0
- package/dist/preflight-CPWpQeBI.js +1 -0
- package/dist/{preflight-BkoSxf4C.js → preflight-D7iFPKeK.js} +8 -7
- package/dist/preflight-D7iFPKeK.js.map +1 -0
- package/dist/prepare-BGjePFQl.js +2 -0
- package/dist/{prepare-CUAWHebF.js → prepare-doGxaAGR.js} +38 -30
- package/dist/prepare-doGxaAGR.js.map +1 -0
- package/dist/{provenance-D7HK3vzt.js → provenance-DmE9uPVt.js} +3 -3
- package/dist/{provenance-D7HK3vzt.js.map → provenance-DmE9uPVt.js.map} +1 -1
- package/dist/provider-DehR4CMN.js +1 -0
- package/dist/providers-gDMko8fl.js +1 -0
- package/dist/providers-tAOD1NQN.js +165 -0
- package/dist/providers-tAOD1NQN.js.map +1 -0
- package/dist/{query-BjBMLa-C.js → query-Bes9Kh6Y.js} +3 -3
- package/dist/{query-BjBMLa-C.js.map → query-Bes9Kh6Y.js.map} +1 -1
- package/dist/query-C_G_svLU.js +1 -0
- package/dist/questions-DjAwMxZ3.js +524 -0
- package/dist/questions-DjAwMxZ3.js.map +1 -0
- package/dist/{record-BRP9vZrj.js → record-BKmMvZ6O.js} +9 -1
- package/dist/{record-BRP9vZrj.js.map → record-BKmMvZ6O.js.map} +1 -1
- package/dist/remote-HDRt16ew.js +378 -0
- package/dist/remote-HDRt16ew.js.map +1 -0
- package/dist/{results-Di7m8dQ3.js → results-DuauQI3Z.js} +4 -4
- package/dist/{results-Di7m8dQ3.js.map → results-DuauQI3Z.js.map} +1 -1
- package/dist/{resume-SUz1GskV.js → resume-BRfa8E_s.js} +10 -10
- package/dist/{resume-SUz1GskV.js.map → resume-BRfa8E_s.js.map} +1 -1
- package/dist/{root-CvdRlRZX.js → root-DICDHCtp.js} +4 -4
- package/dist/{root-CvdRlRZX.js.map → root-DICDHCtp.js.map} +1 -1
- package/dist/{runtime-DdsfnRz_.js → runtime-PX_oXiCS.js} +23 -71
- package/dist/runtime-PX_oXiCS.js.map +1 -0
- package/dist/sandbox-D3A8oUb7.js +70 -0
- package/dist/sandbox-D3A8oUb7.js.map +1 -0
- package/dist/sandbox.d.ts +19 -6
- package/dist/sandbox.js +1 -2
- package/dist/server-C7sg1ndo.js +1 -0
- package/dist/{server-BNnXtmS8.js → server-XDleKaF_.js} +11 -11
- package/dist/server-XDleKaF_.js.map +1 -0
- package/dist/shared-DYpIb1BI.js +166 -0
- package/dist/shared-DYpIb1BI.js.map +1 -0
- package/dist/{source-Dof0NQBm.js → source-C8xumIWj.js} +2 -2
- package/dist/{source-Dof0NQBm.js.map → source-C8xumIWj.js.map} +1 -1
- package/dist/source-fGd0SaKP.js +1 -0
- package/dist/{src-8rRXy4u9.js → src-B7treWa8.js} +1538 -180
- package/dist/src-B7treWa8.js.map +1 -0
- package/dist/{src-C1E9HDWO.js → src-C7_1SqDP.js} +3 -3
- package/dist/{src-C1E9HDWO.js.map → src-C7_1SqDP.js.map} +1 -1
- package/dist/{src-DDNXsD9H.js → src-CF17Ug08.js} +5 -4
- package/dist/src-CF17Ug08.js.map +1 -0
- package/dist/{src-C_4Umgh2.js → src-CFp86GtV.js} +52 -51
- package/dist/src-CFp86GtV.js.map +1 -0
- package/dist/src-C_lPtowT.js +2 -0
- package/dist/src-DIL0jST0.js +368 -0
- package/dist/src-DIL0jST0.js.map +1 -0
- package/dist/src-DNnG4e4R.js +2 -0
- package/dist/src-DTbrY1hi.js +2 -0
- package/dist/src-DdyJEh2v.js +2 -0
- package/dist/src-Dj9PAroc.js +2 -0
- package/dist/src-Du8H85Qq.js +2 -0
- package/dist/src-DvCg0NBs.js +324 -0
- package/dist/src-DvCg0NBs.js.map +1 -0
- package/dist/{src-COgE7991.js → src-DvK0EIZx.js} +5 -5
- package/dist/{src-COgE7991.js.map → src-DvK0EIZx.js.map} +1 -1
- package/dist/{src-DVy58-zZ.js → src-FGXhkEkH.js} +5 -5
- package/dist/{src-DVy58-zZ.js.map → src-FGXhkEkH.js.map} +1 -1
- package/dist/src-jzagbuqH.js +2 -0
- package/dist/src-ofUaYbuY.js +3 -0
- package/dist/src-pJZOrYR9.js +1 -0
- package/dist/{src-D95O-Smv.js → src-u8_ixn_F.js} +15 -11
- package/dist/src-u8_ixn_F.js.map +1 -0
- package/dist/{state-CiXyLqg0.js → state-C__9swoG.js} +35 -23
- package/dist/state-C__9swoG.js.map +1 -0
- package/dist/state-CeR6oz0k.js +66 -0
- package/dist/state-CeR6oz0k.js.map +1 -0
- package/dist/state-CgjQo29K.js +1 -0
- package/dist/studio-app/assets/index-CwQN3f_h.css +2 -0
- package/dist/studio-app/assets/{index-CROH2NB3.js → index-DrQc2Yxd.js} +11 -11
- package/dist/studio-app/index.html +2 -2
- package/dist/{summary-BTksrIxn.js → summary-BLAVLySU.js} +150 -19
- package/dist/summary-BLAVLySU.js.map +1 -0
- package/dist/{summary-2OAWF2N7.js → summary-DgyjUh_l.js} +7 -7
- package/dist/{summary-2OAWF2N7.js.map → summary-DgyjUh_l.js.map} +1 -1
- package/dist/testing.d.ts +3 -2
- package/dist/testing.js +3 -2
- package/dist/{tool-target-BU1JnSE9.js → tool-target-D2mcYlSQ.js} +14 -7
- package/dist/tool-target-D2mcYlSQ.js.map +1 -0
- package/dist/{tools-B10Uj7OI.js → tools-GCyNYSc1.js} +6 -6
- package/dist/tools-GCyNYSc1.js.map +1 -0
- package/dist/transcript-BNNANU8A.js +64 -0
- package/dist/transcript-BNNANU8A.js.map +1 -0
- package/dist/{types-D8RLfkFP.d.ts → types-g17bRAzt.d.ts} +389 -42
- package/dist/{usage-DDF_6tkV.js → usage-CleNKkly.js} +4 -2
- package/dist/{usage-DDF_6tkV.js.map → usage-CleNKkly.js.map} +1 -1
- package/dist/view/index.d.ts +42 -8
- package/dist/view/index.js +6 -6
- package/dist/views-BW6oCDUQ.js +2 -0
- package/dist/{views-Dqa5ojfZ.js → views-BmQF4UCI.js} +13 -8
- package/dist/views-BmQF4UCI.js.map +1 -0
- package/dist/{worker-entry-BAikHl9F.js → worker-entry-yOchGYYv.js} +16 -229
- package/dist/worker-entry-yOchGYYv.js.map +1 -0
- package/dist/{workspace-C-TrNIUc.js → workspace-CgyHEM0l.js} +6 -6
- package/dist/{workspace-C-TrNIUc.js.map → workspace-CgyHEM0l.js.map} +1 -1
- package/dist/workspace-D8sSgpiy.js +1 -0
- package/package.json +12 -12
- package/dist/agent-WSmTaesf.js.map +0 -1
- package/dist/assemble-BCZzTyR7.js.map +0 -1
- package/dist/assemble-C5qq5yT2.js +0 -2
- package/dist/capture-Bp74Z6Fr.js.map +0 -1
- package/dist/capture-DS-cPyuE.js +0 -2
- package/dist/cli-CVrMh_qz.js.map +0 -1
- package/dist/cli-spec-CKEYa8V2.js.map +0 -1
- package/dist/client-Bbo9SPlK.js.map +0 -1
- package/dist/command-BrmXUC-W.js +0 -2
- package/dist/command-C5xFPKHM.js.map +0 -1
- package/dist/command-CzIFp1JQ.js.map +0 -1
- package/dist/command-DUT5l13I.js.map +0 -1
- package/dist/commands-CLmPBrTS.js.map +0 -1
- package/dist/commands-fI3G2Wnh.js +0 -2
- package/dist/config-CGvBeewH.js +0 -457
- package/dist/config-CGvBeewH.js.map +0 -1
- package/dist/context-DbSMEVKs.js.map +0 -1
- package/dist/db-CSSFtGcN.js +0 -3
- package/dist/db-CfQFwJtq.js.map +0 -1
- package/dist/debug-SEmW4w8f.js +0 -2
- package/dist/dist-CTzThfu_.js.map +0 -1
- package/dist/docker-CRQGQbIt.js.map +0 -1
- package/dist/docker-DV2GNgle.js.map +0 -1
- package/dist/docker-Df_YOxxB.js +0 -2
- package/dist/doctor-DlKycx5H.js.map +0 -1
- package/dist/egress-BhzNh7Yi.js.map +0 -1
- package/dist/environment-1jE-HhO-.js +0 -657
- package/dist/environment-1jE-HhO-.js.map +0 -1
- package/dist/errors-C9w_ud7g.js.map +0 -1
- package/dist/errors-yDAEwmZo.js.map +0 -1
- package/dist/fake-instructions-CUI33I_F.js +0 -32
- package/dist/fake-instructions-CUI33I_F.js.map +0 -1
- package/dist/files-AiEKuEZe.js +0 -2
- package/dist/files-BM78_nnF.js +0 -2
- package/dist/files-BMCvfB4X.js +0 -2
- package/dist/harness-CeCp2GSW.js +0 -171
- package/dist/harness-CeCp2GSW.js.map +0 -1
- package/dist/images-BMgdpMAX.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/init-B6vegaP_.js +0 -2
- package/dist/init-BIdLF0F9.js.map +0 -1
- package/dist/jiti-BcJHCtrb.js +0 -46
- package/dist/jiti-BcJHCtrb.js.map +0 -1
- package/dist/live-S0qODb6o.js.map +0 -1
- package/dist/model-B1BZoZ9b.js.map +0 -1
- package/dist/model-BOnlAPzs.js.map +0 -1
- package/dist/model-DWeg5J7z.js +0 -2
- package/dist/model-FXko2kl7.js +0 -2
- package/dist/node-Co7EXge0.js.map +0 -1
- package/dist/opencode-providers-ByD85Q0l.js.map +0 -1
- package/dist/options-CKqyPGxX.js +0 -739
- package/dist/options-CKqyPGxX.js.map +0 -1
- package/dist/plan-DUydFc_1.js.map +0 -1
- package/dist/preflight-BkoSxf4C.js.map +0 -1
- package/dist/preflight-D5LW7Oqi.js +0 -2
- package/dist/prepare-CUAWHebF.js.map +0 -1
- package/dist/prepare-oji2oO8W.js +0 -3
- package/dist/provider-BDbNOQhQ.js +0 -2
- package/dist/providers-B1DzMvmH.js +0 -107
- package/dist/providers-B1DzMvmH.js.map +0 -1
- package/dist/providers-CpEfNU1F.js +0 -2
- package/dist/query-oPO_XrTy.js +0 -2
- package/dist/questions-B8by4Bbt.js +0 -234
- package/dist/questions-B8by4Bbt.js.map +0 -1
- package/dist/runtime-DdsfnRz_.js.map +0 -1
- package/dist/sandbox-D5v-of6d.js +0 -139
- package/dist/sandbox-D5v-of6d.js.map +0 -1
- package/dist/server-BNnXtmS8.js.map +0 -1
- package/dist/server-BNwoKzlm.js +0 -2
- package/dist/source-Bk_0ZaEY.js +0 -2
- package/dist/src-8rRXy4u9.js.map +0 -1
- package/dist/src-BGWuWp6I.js +0 -3
- package/dist/src-BRt0O02i.js +0 -3
- package/dist/src-Bx2x1ekD.js +0 -3
- package/dist/src-C_4Umgh2.js.map +0 -1
- package/dist/src-D95O-Smv.js.map +0 -1
- package/dist/src-DDNXsD9H.js.map +0 -1
- package/dist/src-DLa-ESW0.js +0 -4
- package/dist/src-cN0aB-WR.js +0 -3
- package/dist/src-cdIVjC5b.js +0 -2
- package/dist/src-sIsDHWYq.js +0 -3
- package/dist/state-Bo5zg4da.js +0 -2
- package/dist/state-CiXyLqg0.js.map +0 -1
- package/dist/studio-app/assets/index-B9MCTfuu.css +0 -2
- package/dist/summary-BTksrIxn.js.map +0 -1
- package/dist/tool-target-BU1JnSE9.js.map +0 -1
- package/dist/tools-B10Uj7OI.js.map +0 -1
- package/dist/views-Cs2Oqayd.js +0 -3
- package/dist/views-Dqa5ojfZ.js.map +0 -1
- package/dist/worker-entry-BAikHl9F.js.map +0 -1
- package/dist/workspace-Do_SiWBs.js +0 -2
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
import { t as isObjectLike } from "./object-CLf2-tNq.js";
|
|
2
|
+
import { n as UsageError } from "./errors-B6BSMs03.js";
|
|
3
|
+
import { t as parseDuration } from "./duration-CEOQww97.js";
|
|
4
|
+
import { a as fieldPath, f as valueAt, i as durationSchema, o as genericProblem, s as got, t as closest, u as oneOf } from "./shared-DYpIb1BI.js";
|
|
5
|
+
import { p as harnessDescriptorSchema } from "./harness-B8QC9u0O.js";
|
|
6
|
+
import { A as questionOptionsSchema, C as configJudgeDataSchema, i as checkOptions, r as checkHarnesses, u as profileJudge, w as judgeProblem } from "./options-Bh2W63dK.js";
|
|
7
|
+
import { E as sandboxPaths, F as envVarsSchema, I as gatePolicySchema, M as environmentDataSchema, N as sandboxDescriptorSchema, P as egressPolicySchema, _ as isEnvironment, b as isSecret, d as environmentFrom, f as environmentPaths, g as helpersFor } from "./environment-DNjRk0nU.js";
|
|
8
|
+
import { a as fromCwd } from "./ids-Bp6ut7ZD.js";
|
|
9
|
+
import { i as checkModelString, o as judgeCredentials } from "./judge-DW_GDOmB.js";
|
|
10
|
+
import { t as inCI } from "./env-BQqj93gu.js";
|
|
11
|
+
import { u as ownFileURL } from "./root-DICDHCtp.js";
|
|
12
|
+
import { c as DEFAULT_HARNESS, g as isAgentKind, l as HARNESS_EXAMPLE, m as credentialOf, s as AGENTS } from "./egress-C4PZTSSe.js";
|
|
13
|
+
import { a as reevalJiti, n as defaultIncludeGlobs } from "./adapter-DWlpOtRn.js";
|
|
14
|
+
import { t as docker } from "./sandbox-D3A8oUb7.js";
|
|
15
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
16
|
+
import { availableParallelism, totalmem } from "node:os";
|
|
17
|
+
import { dirname, join, resolve } from "node:path";
|
|
18
|
+
import { parseDocument } from "yaml";
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
//#region src/contract/config.ts
|
|
21
|
+
const count = () => z.number().int().min(1);
|
|
22
|
+
const captureWindowSchema = z.strictObject({
|
|
23
|
+
width: count().describe("In CSS pixels."),
|
|
24
|
+
height: count().describe("In CSS pixels.")
|
|
25
|
+
}).describe("Live tab size for `c.capture()`. Default 1280×720 when the call and cascade omit `viewport`.");
|
|
26
|
+
const captureOptionsSchema = z.strictObject({
|
|
27
|
+
viewport: z.union([captureWindowSchema, z.literal("full-page")]).optional().describe("The window and saved PNG size, or `'full-page'`. Default 1280×720."),
|
|
28
|
+
timeout: durationSchema.optional().describe("How long to poll before failing. Default `'60s'`."),
|
|
29
|
+
browser: z.boolean().optional().describe("`false` refuses the host `chrome-headless-shell` binary. `c.capture()` then throws."),
|
|
30
|
+
model: z.string().includes("/").optional().describe("Vision model for `instructions`. Cascades like `question`. Default is the analysis model when that model accepts images; never Jev.")
|
|
31
|
+
}).describe("Camera defaults for `c.capture()`. Cascades config → profile defaults → group → eval, shallow-merged. `viewport` on a level replaces the whole value. `port` is not an option: pass it as the first argument to `c.capture`.");
|
|
32
|
+
const harnessList = z.union([harnessDescriptorSchema, z.array(harnessDescriptorSchema).min(1)]);
|
|
33
|
+
const budgetSchema = z.strictObject({
|
|
34
|
+
attempts: count().optional().describe("The most attempts the plan may have."),
|
|
35
|
+
duration: durationSchema.optional().describe("How long the run may go on, from the first attempt's start.")
|
|
36
|
+
}).describe("Run-level caps. `attempts` is checked against the plan; `duration` while running.");
|
|
37
|
+
const historyConfigSchema = z.strictObject({
|
|
38
|
+
dir: z.string().optional().describe("Committed history folder (default: `.history` in the directory the evals live in)."),
|
|
39
|
+
artifacts: z.enum([
|
|
40
|
+
"all",
|
|
41
|
+
"failures",
|
|
42
|
+
"none"
|
|
43
|
+
]).optional().describe("Artifacts recorded next to each run: every eval's, failing evals' only, or none.")
|
|
44
|
+
});
|
|
45
|
+
const recordMode = z.enum([
|
|
46
|
+
"manual",
|
|
47
|
+
"ci",
|
|
48
|
+
"always"
|
|
49
|
+
]);
|
|
50
|
+
const profileDefaultsDataSchema = z.strictObject({
|
|
51
|
+
harness: harnessList.optional().describe("The agents every eval runs on, unless a group or eval replaces them."),
|
|
52
|
+
sandbox: sandboxDescriptorSchema.optional().describe("The default machine when the resolved environment omits `sandbox`."),
|
|
53
|
+
environment: environmentDataSchema.optional().describe("What every eval runs in when it names no environment."),
|
|
54
|
+
runs: count().optional().describe("Attempts per cell."),
|
|
55
|
+
gate: gatePolicySchema.optional().describe("How each cell's attempts decide its outcome."),
|
|
56
|
+
env: envVarsSchema.optional().describe("Variables and secrets for the sandbox, merged by name over the config's."),
|
|
57
|
+
egress: egressPolicySchema.optional().describe("Where the sandbox may connect; replaces the config's policy."),
|
|
58
|
+
judge: configJudgeDataSchema.optional().describe("Defaults for `c.judge()`, merged field by field over the config's."),
|
|
59
|
+
timeout: durationSchema.optional().describe("Per attempt."),
|
|
60
|
+
capture: captureOptionsSchema.optional().describe("Screenshot defaults for `c.capture()`, shallow-merged over the config's."),
|
|
61
|
+
question: questionOptionsSchema.optional().describe("Defaults for every compare question, merged over the config's.")
|
|
62
|
+
}).describe("`ProfileDefaults` as data: the environment without `extend`, and the judge as data.");
|
|
63
|
+
const profileLimitSchema = z.strictObject({
|
|
64
|
+
runs: count().optional().describe("The most runs per cell."),
|
|
65
|
+
compare: z.union([z.literal(false), z.strictObject({ maxVariants: z.number().int().min(0) })]).optional().describe("`false` runs the control only; `{ maxVariants }` keeps the first n variants."),
|
|
66
|
+
harness: z.array(z.string()).optional().describe("Harness ids to allow; evals left with none are skipped.")
|
|
67
|
+
}).describe("Over every eval, and it can only narrow; `pin` on a group or eval skips it.");
|
|
68
|
+
const profileDataSchema = z.strictObject({
|
|
69
|
+
defaults: profileDefaultsDataSchema.optional().describe("What evals get when they don't say otherwise: under groups and evals."),
|
|
70
|
+
limit: profileLimitSchema.optional().describe("Caps over every eval, which only narrow; `pin` on a group or eval skips them."),
|
|
71
|
+
judge: configJudgeDataSchema.optional().describe("Judge settings for this profile, the same as `defaults.judge`, which wins field by field."),
|
|
72
|
+
budget: budgetSchema.optional().describe("Merged over the config's `budget`."),
|
|
73
|
+
record: recordMode.optional().describe("When this profile's runs are copied into the history folder."),
|
|
74
|
+
tags: z.array(z.string()).optional().describe("Tag filters for this profile, like `--tag` (`'!slow'` excludes).")
|
|
75
|
+
}).describe("`Profile` as data: its defaults and judge as data.");
|
|
76
|
+
const fakeVote = z.enum([
|
|
77
|
+
"A",
|
|
78
|
+
"B",
|
|
79
|
+
"tie"
|
|
80
|
+
]);
|
|
81
|
+
const failure = z.strictObject({ error: z.string() });
|
|
82
|
+
const fakeAnalysisDataSchema = z.strictObject({
|
|
83
|
+
kind: z.literal("fake-analysis"),
|
|
84
|
+
output: z.unknown().optional().describe("The model's structured answer."),
|
|
85
|
+
note: z.union([z.string(), failure]).optional().describe("`analysis.agent`'s note on each eval and harness, or an error to throw."),
|
|
86
|
+
vote: z.union([
|
|
87
|
+
fakeVote,
|
|
88
|
+
z.strictObject({
|
|
89
|
+
winner: fakeVote,
|
|
90
|
+
reason: z.string().optional()
|
|
91
|
+
}),
|
|
92
|
+
failure
|
|
93
|
+
]).optional().describe("How it answers compare questions: a fixed pick, or `{ error }` to fail every call.")
|
|
94
|
+
}).describe("`fakeAnalysis()` as data: canned answers, without the functions of the digest or pair.");
|
|
95
|
+
const analysisConfigDataSchema = z.strictObject({
|
|
96
|
+
model: z.union([z.string(), fakeAnalysisDataSchema]).optional().describe("`'provider/model'`, e.g. `'google/gemini-3.8-flash'`; default: the judge's, if it's one."),
|
|
97
|
+
agent: z.boolean().optional().describe("`reeval run` writes a note on each eval and harness once its attempts finish, with the analysis model (default false: one call per eval and harness, every run)."),
|
|
98
|
+
instructions: z.string().optional().describe("Added to the prompt, e.g. \"Fewer tool calls count as a win. The skill under test is reeval-good.\""),
|
|
99
|
+
maxInputTokens: z.number().int().min(1e3).optional().describe("The digest's cap; evidence is dropped, lowest priority first, to fit (default 20000)."),
|
|
100
|
+
maxOutputTokens: z.number().int().min(500).optional().describe("The most tokens the model may answer with (default 8000). reeval has no prices, so tokens are the cap.")
|
|
101
|
+
}).describe("`AnalysisConfig` as data: the model by name, or a fake analysis.");
|
|
102
|
+
const reevalConfigDataSchema = z.strictObject({
|
|
103
|
+
include: z.array(z.string()).optional().describe("Globs of eval files, relative to the config's directory; default every `.eval.ts` under `evals`."),
|
|
104
|
+
harness: harnessList.optional().describe("The agents every eval runs on, unless a group or eval replaces them; default Cursor."),
|
|
105
|
+
sandbox: sandboxDescriptorSchema.optional().describe("The default machine when the resolved environment omits `sandbox`. Not an environment: it does not invent skills, files or a name. Implicit `docker()` when nobody sets one."),
|
|
106
|
+
environment: environmentDataSchema.optional().describe("What every eval runs in when it names no environment, and a baseline-less compare's control."),
|
|
107
|
+
runs: count().optional().describe("Attempts per cell; default 1."),
|
|
108
|
+
gate: gatePolicySchema.optional().describe("How each cell's attempts decide its outcome; default `'all'`."),
|
|
109
|
+
env: envVarsSchema.optional().describe("Variables and secrets for the sandbox, merged by name down the cascade."),
|
|
110
|
+
egress: egressPolicySchema.optional().describe("Where the sandbox may connect; default `'allow-all'`."),
|
|
111
|
+
judge: configJudgeDataSchema.optional().describe("Defaults for `c.judge()`, merged field by field down the cascade."),
|
|
112
|
+
timeout: durationSchema.optional().describe("Per attempt; default `'15m'`."),
|
|
113
|
+
capture: captureOptionsSchema.optional().describe("Screenshot defaults for `c.capture()`. `REEVAL_CAPTURE_BROWSER=0` refuses the host binary when `c.capture()` runs; it does not change this object."),
|
|
114
|
+
question: questionOptionsSchema.optional().describe("Defaults for every compare question: the model that answers it and what each side shows. Merged down the cascade."),
|
|
115
|
+
concurrency: count().optional().describe("How many attempts run at once. `--concurrency` and `REEVAL_CONCURRENCY` override it."),
|
|
116
|
+
record: recordMode.optional().describe("When runs are copied into the history folder: `reeval record` only, in CI, or on every run."),
|
|
117
|
+
history: historyConfigSchema.optional().describe("The history folder, and which attempts keep their artifacts there."),
|
|
118
|
+
budget: budgetSchema.optional().describe("Caps on a run; a profile's `budget` is merged over it."),
|
|
119
|
+
profiles: z.record(z.string(), profileDataSchema).optional().describe("Named presets of `defaults`, `limit`, `judge`, `budget`, `record` and `tags`."),
|
|
120
|
+
defaultProfile: z.strictObject({
|
|
121
|
+
ci: z.string().optional(),
|
|
122
|
+
local: z.string().optional()
|
|
123
|
+
}).optional().describe("The profile when neither `--profile` nor `REEVAL_PROFILE` names one: `ci` when `CI` is set."),
|
|
124
|
+
secrets: z.strictObject({ unbrokered: z.enum(["warn", "error"]).optional() }).optional().describe("`unbrokered: 'error'` refuses Docker secrets without `hosts` instead of warning (default `'warn'`)."),
|
|
125
|
+
debug: z.strictObject({
|
|
126
|
+
keep: count().optional(),
|
|
127
|
+
traceBufferMb: z.number().positive().optional()
|
|
128
|
+
}).optional().describe("Local debug data in `.reeval/debug`: how many runs to keep (default 20), and how many MB of raw trace each attempt buffers for trace.ndjson (default 8; the oldest entries go first)."),
|
|
129
|
+
analysis: analysisConfigDataSchema.optional().describe("`reeval analyze`, compare questions without a model, and `agent`'s notes during a run.")
|
|
130
|
+
}).describe("`ReevalConfig` in its data forms, as `reeval.yaml` or another language carries it: the environment without `extend`, judge and analysis models by name or as fakes with fixed answers, and profiles the same way. AI SDK model objects and fake score functions are TypeScript only.");
|
|
131
|
+
/** The object schema an issue's path leads to through `schema`, for "Did you mean". */
|
|
132
|
+
function keysAt(schema, path, value) {
|
|
133
|
+
const def = schema.def;
|
|
134
|
+
if (def.innerType) return keysAt(def.innerType, path, value);
|
|
135
|
+
if (def.getter) return keysAt(def.getter(), path, value);
|
|
136
|
+
if (def.options) return [...new Set(def.options.flatMap((option) => keysAt(option, path, value)))];
|
|
137
|
+
const [key, ...rest] = path;
|
|
138
|
+
if (key === void 0) return def.shape ? Object.keys(def.shape) : [];
|
|
139
|
+
const next = valueAt(value, [key]);
|
|
140
|
+
if (def.shape) {
|
|
141
|
+
const field = def.shape[String(key)];
|
|
142
|
+
return field ? keysAt(field, rest, next) : [];
|
|
143
|
+
}
|
|
144
|
+
const inner = def.valueType ?? def.element;
|
|
145
|
+
return inner ? keysAt(inner, rest, next) : [];
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The issue under a union that went furthest into the value: the branch the value was most
|
|
149
|
+
* likely meant to be. A union where every branch failed at its own path is left as it is.
|
|
150
|
+
*/
|
|
151
|
+
function deepest(issue) {
|
|
152
|
+
if (issue.code !== "invalid_union" || !("errors" in issue) || !issue.errors.length) return issue;
|
|
153
|
+
let best;
|
|
154
|
+
for (const branch of issue.errors) {
|
|
155
|
+
const inner = branch[0];
|
|
156
|
+
if (!inner) continue;
|
|
157
|
+
const found = deepest({
|
|
158
|
+
...inner,
|
|
159
|
+
path: [...issue.path, ...inner.path]
|
|
160
|
+
});
|
|
161
|
+
if (!best || found.path.length > best.path.length) best = found;
|
|
162
|
+
}
|
|
163
|
+
return best && best.path.length > issue.path.length ? best : issue;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* reeval's sentence for what the config contract refuses in `value` (the parsed `reeval.yaml`,
|
|
167
|
+
* or a config another language sent), naming the field by its path, or undefined.
|
|
168
|
+
*/
|
|
169
|
+
function configDataProblem(value, file) {
|
|
170
|
+
const result = reevalConfigDataSchema.safeParse(value);
|
|
171
|
+
if (result.success) return void 0;
|
|
172
|
+
const { issues } = result.error;
|
|
173
|
+
const issue = deepest(issues.find((i) => i.code === "unrecognized_keys") ?? issues[0]);
|
|
174
|
+
const field = fieldPath(issue.path);
|
|
175
|
+
const at = field ? `${field} in ${file}` : file;
|
|
176
|
+
const v = valueAt(value, issue.path);
|
|
177
|
+
if (issue.code === "unrecognized_keys") {
|
|
178
|
+
const key = issue.keys[0];
|
|
179
|
+
const near = closest(key, keysAt(reevalConfigDataSchema, issue.path, value));
|
|
180
|
+
return `Unknown option "${key}" in ${at}.${near ? ` Did you mean "${near}"?` : ""}`;
|
|
181
|
+
}
|
|
182
|
+
if (issue.code === "invalid_union") {
|
|
183
|
+
const values = "options" in issue ? issue.options : void 0;
|
|
184
|
+
return values ? `${at} is ${oneOf(values)} (got ${got(v)})` : `${at} matches none of its forms (got ${got(v)})`;
|
|
185
|
+
}
|
|
186
|
+
if (issue.code === "invalid_type" && v === void 0) return `${at} is missing`;
|
|
187
|
+
if ((issue.code === "too_small" || issue.code === "too_big") && issue.origin === "number") {
|
|
188
|
+
const small = issue.code === "too_small";
|
|
189
|
+
const bound = small ? issue.minimum : issue.maximum;
|
|
190
|
+
const strict = small ? "above" : "below";
|
|
191
|
+
return `${at} must be ${issue.inclusive === false ? strict : small ? "at least" : "at most"} ${String(bound)} (got ${got(v)})`;
|
|
192
|
+
}
|
|
193
|
+
return `${at}${genericProblem("", {
|
|
194
|
+
...issue,
|
|
195
|
+
path: []
|
|
196
|
+
}, v)}`;
|
|
197
|
+
}
|
|
198
|
+
//#endregion
|
|
199
|
+
//#region src/config.ts
|
|
200
|
+
const CONFIG_KEYS = [
|
|
201
|
+
"include",
|
|
202
|
+
"harness",
|
|
203
|
+
"sandbox",
|
|
204
|
+
"environment",
|
|
205
|
+
"runs",
|
|
206
|
+
"gate",
|
|
207
|
+
"env",
|
|
208
|
+
"egress",
|
|
209
|
+
"judge",
|
|
210
|
+
"capture",
|
|
211
|
+
"question",
|
|
212
|
+
"timeout",
|
|
213
|
+
"concurrency",
|
|
214
|
+
"record",
|
|
215
|
+
"history",
|
|
216
|
+
"budget",
|
|
217
|
+
"profiles",
|
|
218
|
+
"defaultProfile",
|
|
219
|
+
"secrets",
|
|
220
|
+
"debug",
|
|
221
|
+
"analysis"
|
|
222
|
+
];
|
|
223
|
+
const PROFILE_KEYS = [
|
|
224
|
+
"defaults",
|
|
225
|
+
"limit",
|
|
226
|
+
"judge",
|
|
227
|
+
"budget",
|
|
228
|
+
"record",
|
|
229
|
+
"tags"
|
|
230
|
+
];
|
|
231
|
+
const DEFAULTS_KEYS = [
|
|
232
|
+
"harness",
|
|
233
|
+
"sandbox",
|
|
234
|
+
"environment",
|
|
235
|
+
"runs",
|
|
236
|
+
"gate",
|
|
237
|
+
"env",
|
|
238
|
+
"egress",
|
|
239
|
+
"judge",
|
|
240
|
+
"capture",
|
|
241
|
+
"question",
|
|
242
|
+
"timeout"
|
|
243
|
+
];
|
|
244
|
+
const LIMIT_KEYS = [
|
|
245
|
+
"runs",
|
|
246
|
+
"compare",
|
|
247
|
+
"harness"
|
|
248
|
+
];
|
|
249
|
+
/**
|
|
250
|
+
* The config: a plain object, or a callback that destructures the helpers it needs, e.g.
|
|
251
|
+
* `defineConfig(({ secret }) => ({ env: { KEY: secret.fromEnv('KEY') } }))`.
|
|
252
|
+
*/
|
|
253
|
+
const defineConfig = (config) => config;
|
|
254
|
+
/** The script configs `reeval` looks for in each directory, first match first, as Vitest orders its own. */
|
|
255
|
+
const SCRIPT_FILES = [
|
|
256
|
+
"ts",
|
|
257
|
+
"mts",
|
|
258
|
+
"cts",
|
|
259
|
+
"js",
|
|
260
|
+
"mjs",
|
|
261
|
+
"cjs"
|
|
262
|
+
].map((ext) => `reeval.config.${ext}`);
|
|
263
|
+
/** The data configs, for projects without TypeScript: one of them, and no script config beside it. */
|
|
264
|
+
const YAML_FILES = ["reeval.yaml", "reeval.yml"];
|
|
265
|
+
/** Every file name `reeval` looks for in each directory. */
|
|
266
|
+
const CONFIG_FILES = [...SCRIPT_FILES, ...YAML_FILES];
|
|
267
|
+
/** True for a `reeval.yaml`-style config, which is data rather than a module. */
|
|
268
|
+
const isYamlConfig = (path) => /\.ya?ml$/i.test(path);
|
|
269
|
+
const DEFAULT_CONFIG_FILE = "reeval.config.ts";
|
|
270
|
+
/** The names `findConfig` looks for, as doctor and the missing-config error list them. */
|
|
271
|
+
const configFilesPhrase = () => `${CONFIG_FILES[0]} (or ${CONFIG_FILES.slice(1).join(", ")})`;
|
|
272
|
+
/** Usage error when `findConfig()` finds nothing: same names as doctor, plus `reeval init`. */
|
|
273
|
+
const missingConfigMessage = () => `No ${configFilesPhrase()} here or above. Run reeval init.`;
|
|
274
|
+
/** A found config path, or the missing-config usage error. */
|
|
275
|
+
function requireConfig(path) {
|
|
276
|
+
if (path === void 0) throw new UsageError(missingConfigMessage());
|
|
277
|
+
return path;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* The nearest config file in `from` or a directory above it, up to the filesystem root, the way
|
|
281
|
+
* Vitest and ESLint find theirs. A git or workspace root doesn't stop the search: a nested
|
|
282
|
+
* project's own config is nearer, so it wins.
|
|
283
|
+
*/
|
|
284
|
+
function findConfig(from = process.cwd()) {
|
|
285
|
+
for (let dir = resolve(from);; dir = dirname(dir)) {
|
|
286
|
+
const script = SCRIPT_FILES.find((name) => existsSync(join(dir, name)));
|
|
287
|
+
const yaml = YAML_FILES.filter((name) => existsSync(join(dir, name)));
|
|
288
|
+
const found = [...script ? [script] : [], ...yaml];
|
|
289
|
+
if (found.length > 1) {
|
|
290
|
+
const where = fromCwd(dir) || ".";
|
|
291
|
+
throw new UsageError(`${found.join(" and ")} are both in ${where}: reeval reads one config per directory. Keep one, or pass --config.`);
|
|
292
|
+
}
|
|
293
|
+
if (found[0]) return join(dir, found[0]);
|
|
294
|
+
if (dirname(dir) === dir) return void 0;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/** The config a command reads: `--config` (from the current directory), else the nearest one. */
|
|
298
|
+
const configPathFor = (flag) => flag === void 0 ? findConfig() : resolve(flag);
|
|
299
|
+
/** The config file as the user would type it from the current directory. */
|
|
300
|
+
const configLabel = (path) => fromCwd(path) || path;
|
|
301
|
+
/**
|
|
302
|
+
* Loads the config at `path` (see `configPathFor`); its directory is the project root. Without a
|
|
303
|
+
* path, every key takes its default and the current directory is the root. `reeval run` and
|
|
304
|
+
* `reeval image build` refuse a missing file before this.
|
|
305
|
+
*/
|
|
306
|
+
async function loadConfig(path, command = "run", profile = void 0) {
|
|
307
|
+
if (path === void 0) return resolveConfig({}, process.cwd(), DEFAULT_CONFIG_FILE);
|
|
308
|
+
const root = dirname(path);
|
|
309
|
+
const configPath = configLabel(path);
|
|
310
|
+
if (!existsSync(path)) throw new UsageError(`Config file not found: ${configPath}`);
|
|
311
|
+
if (isYamlConfig(path)) return resolveConfigData(parseYamlConfig(readFileSync(path, "utf8"), configPath), root, configPath);
|
|
312
|
+
const module = await (await reevalJiti(ownFileURL("dist/cli.js").href, { moduleCache: false })).import(path);
|
|
313
|
+
if (!module || typeof module !== "object" || !("default" in module)) {
|
|
314
|
+
const names = module && typeof module === "object" ? Object.keys(module) : [];
|
|
315
|
+
const exportsNote = names.length ? ` (it exports ${names.join(", ")})` : "";
|
|
316
|
+
throw new UsageError(`${configPath} has no default export${exportsNote}. Write export default defineConfig({…}).`);
|
|
317
|
+
}
|
|
318
|
+
let exported = module.default;
|
|
319
|
+
if (typeof exported === "function") {
|
|
320
|
+
const hostEnv = process.env;
|
|
321
|
+
exported = exported({
|
|
322
|
+
...helpersFor(root),
|
|
323
|
+
ci: inCI(hostEnv),
|
|
324
|
+
command,
|
|
325
|
+
hostEnv,
|
|
326
|
+
profile
|
|
327
|
+
});
|
|
328
|
+
if (exported instanceof Promise) throw new UsageError(`The defineConfig callback in ${configPath} returned a Promise. It runs once when the config loads and must return the config synchronously.`);
|
|
329
|
+
}
|
|
330
|
+
if (!exported || typeof exported !== "object" || exported instanceof Promise) throw new UsageError(`${configPath}'s default export must be defineConfig({…}) (got ${exported instanceof Promise ? "a Promise" : JSON.stringify(exported)}). Write export default defineConfig({…}).`);
|
|
331
|
+
return resolveConfig(exported, root, configPath);
|
|
332
|
+
}
|
|
333
|
+
/** A `reeval.yaml`'s text as data: an empty file is an empty config. */
|
|
334
|
+
function parseYamlConfig(text, file) {
|
|
335
|
+
const doc = parseDocument(text, { prettyErrors: true });
|
|
336
|
+
const [error] = doc.errors;
|
|
337
|
+
if (error) throw new UsageError(`${file} isn't valid YAML: ${error.message.trimEnd()}`);
|
|
338
|
+
return doc.toJS() ?? {};
|
|
339
|
+
}
|
|
340
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
341
|
+
/** A config's Dockerfile that isn't there, named by its field. */
|
|
342
|
+
const missingIn = (file) => (field, full) => {
|
|
343
|
+
throw new UsageError(`${field} in ${file}: ${full} doesn't exist. Paths in ${file} are relative to its directory.`);
|
|
344
|
+
};
|
|
345
|
+
/**
|
|
346
|
+
* The config with `sandbox` and each profile's `defaults.sandbox`, when written as data, given
|
|
347
|
+
* absolute host paths against `root`. With `environments`, an environment written as data (a
|
|
348
|
+
* `reeval.yaml`'s) gets its layers' paths resolved too; a script config's come from
|
|
349
|
+
* `defineEnvironment`, which resolves them against its own file.
|
|
350
|
+
*/
|
|
351
|
+
function withHostPaths(config, root, file, { environments = false } = {}) {
|
|
352
|
+
const missing = missingIn(file);
|
|
353
|
+
const options = (value, prefix) => {
|
|
354
|
+
if (!isRecord(value)) return value;
|
|
355
|
+
const out = { ...value };
|
|
356
|
+
if ("sandbox" in value) out.sandbox = sandboxPaths(value.sandbox, root, `${prefix}sandbox`, missing);
|
|
357
|
+
if (environments && "environment" in value) out.environment = environmentPaths(value.environment, root, `${prefix}environment`, missing);
|
|
358
|
+
return out;
|
|
359
|
+
};
|
|
360
|
+
const out = options(config, "");
|
|
361
|
+
const { profiles } = out;
|
|
362
|
+
if (!isRecord(profiles)) return out;
|
|
363
|
+
const each = Object.entries(profiles).map(([name, profile]) => isRecord(profile) && "defaults" in profile ? [name, {
|
|
364
|
+
...profile,
|
|
365
|
+
defaults: options(profile.defaults, `profiles.${name}.defaults.`)
|
|
366
|
+
}] : [name, profile]);
|
|
367
|
+
return {
|
|
368
|
+
...out,
|
|
369
|
+
profiles: Object.fromEntries(each)
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
/** The config from its data form: each environment's data becomes an `Environment` again. */
|
|
373
|
+
function configFromData(data) {
|
|
374
|
+
const withEnvironment = (options) => {
|
|
375
|
+
if (!isObjectLike(options)) return options;
|
|
376
|
+
const { environment } = options;
|
|
377
|
+
return isEnvironment(environment) ? {
|
|
378
|
+
...options,
|
|
379
|
+
environment: environmentFrom(environment)
|
|
380
|
+
} : options;
|
|
381
|
+
};
|
|
382
|
+
const config = withEnvironment(data);
|
|
383
|
+
const { profiles } = config;
|
|
384
|
+
if (typeof profiles !== "object" || profiles === null || Array.isArray(profiles)) return config;
|
|
385
|
+
const each = Object.entries(profiles).map(([name, profile]) => {
|
|
386
|
+
if (!isObjectLike(profile)) return [name, profile];
|
|
387
|
+
const { defaults } = profile;
|
|
388
|
+
return [name, defaults === void 0 ? profile : {
|
|
389
|
+
...profile,
|
|
390
|
+
defaults: withEnvironment(defaults)
|
|
391
|
+
}];
|
|
392
|
+
});
|
|
393
|
+
return {
|
|
394
|
+
...config,
|
|
395
|
+
profiles: Object.fromEntries(each)
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Resolves a config in its data form (a parsed `reeval.yaml`, or `ReevalConfigData` from another
|
|
400
|
+
* language) whose relative host paths resolve against `root`, its directory: `resolveConfig`'s
|
|
401
|
+
* checks name a mistake in their own words first, and the config contract refuses anything they
|
|
402
|
+
* let through, such as a `null` or a misspelt nested key.
|
|
403
|
+
*/
|
|
404
|
+
function resolveConfigData(input, root, file) {
|
|
405
|
+
if (!isRecord(input)) throw new UsageError(`${file} must be a mapping of config keys, like harness: and judge: (got ${JSON.stringify(input)})`);
|
|
406
|
+
const data = withHostPaths(input, root, file, { environments: true });
|
|
407
|
+
const { environment, profiles } = data;
|
|
408
|
+
if ([environment, ...Object.values(typeof profiles === "object" && profiles ? profiles : {}).map((p) => p?.defaults?.environment)].some((e) => e !== void 0 && !isEnvironment(e))) {
|
|
409
|
+
const problem = configDataProblem(data, file);
|
|
410
|
+
if (problem) throw new UsageError(problem);
|
|
411
|
+
}
|
|
412
|
+
let resolved;
|
|
413
|
+
try {
|
|
414
|
+
resolved = resolveConfig(configFromData(data), root, file);
|
|
415
|
+
} catch (error) {
|
|
416
|
+
if (error instanceof UsageError) throw error;
|
|
417
|
+
const problem = configDataProblem(data, file);
|
|
418
|
+
if (problem) throw new UsageError(problem);
|
|
419
|
+
throw error;
|
|
420
|
+
}
|
|
421
|
+
const problem = configDataProblem(data, file);
|
|
422
|
+
if (problem) throw new UsageError(problem);
|
|
423
|
+
return resolved;
|
|
424
|
+
}
|
|
425
|
+
const GLOB = /[*?[\]{}!]/;
|
|
426
|
+
/** The directory every include pattern starts in: `evals/**\/*.eval.ts` → `evals`. */
|
|
427
|
+
function evalRoot(include) {
|
|
428
|
+
const dirs = include.map((pattern) => {
|
|
429
|
+
const parts = pattern.split("/");
|
|
430
|
+
const stop = parts.findIndex((part) => GLOB.test(part));
|
|
431
|
+
return (stop < 0 ? parts.slice(0, -1) : parts.slice(0, stop)).filter((p) => p && p !== ".");
|
|
432
|
+
});
|
|
433
|
+
const common = [];
|
|
434
|
+
for (let i = 0; dirs.every((d) => d[i] !== void 0 && d[i] === dirs[0]?.[i]); i++) common.push(dirs[0]?.[i]);
|
|
435
|
+
return common.join("/");
|
|
436
|
+
}
|
|
437
|
+
const ARTIFACTS = [
|
|
438
|
+
"all",
|
|
439
|
+
"failures",
|
|
440
|
+
"none"
|
|
441
|
+
];
|
|
442
|
+
const RECORD = [
|
|
443
|
+
"manual",
|
|
444
|
+
"ci",
|
|
445
|
+
"always"
|
|
446
|
+
];
|
|
447
|
+
const unknownKey = (value, allowed, where, file) => {
|
|
448
|
+
const key = Object.keys(value).find((k) => !allowed.includes(k));
|
|
449
|
+
if (key === void 0) return;
|
|
450
|
+
const hints = {
|
|
451
|
+
reuse: "Reusing control results from .history isn’t built yet; every run runs its controls.",
|
|
452
|
+
compare: "compare only goes on a group or eval; a profile can narrow it with limit.compare."
|
|
453
|
+
};
|
|
454
|
+
const near = closest(key, allowed);
|
|
455
|
+
const hint = hints[key] ?? (near ? `Did you mean "${near}"?` : `Use ${allowed.join(", ")}.`);
|
|
456
|
+
throw new UsageError(`Unknown option "${key}" in ${where} in ${file}. ${hint}`);
|
|
457
|
+
};
|
|
458
|
+
function checkBudget(budget, where, file) {
|
|
459
|
+
if (budget === void 0) return {};
|
|
460
|
+
if (typeof budget !== "object" || budget === null) throw new UsageError(`${where} in ${file} must be an object like { attempts: 20, duration: '1h' } (got ${JSON.stringify(budget)})`);
|
|
461
|
+
if ("usd" in budget || "tokens" in budget) throw new UsageError(`${where}.${"usd" in budget ? "usd" : "tokens"} in ${file} isn't supported yet: reeval records the tokens harnesses report, but prices none and enforces no token budget. Cap the run with attempts or duration.`);
|
|
462
|
+
unknownKey(budget, ["attempts", "duration"], where, file);
|
|
463
|
+
const { attempts, duration } = budget;
|
|
464
|
+
if (attempts !== void 0 && !(Number.isInteger(attempts) && attempts >= 1)) throw new UsageError(`${where}.attempts in ${file} must be a whole number of at least 1 (got ${JSON.stringify(attempts)})`);
|
|
465
|
+
if (duration !== void 0) parseDuration(duration);
|
|
466
|
+
return {
|
|
467
|
+
...attempts ? { attempts } : {},
|
|
468
|
+
...duration ? { duration } : {}
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
const describeValue = (v) => {
|
|
472
|
+
if (!isObjectLike(v)) return JSON.stringify(v);
|
|
473
|
+
const { kind } = v;
|
|
474
|
+
if (kind === "fake") return "fakeHarness({ … })";
|
|
475
|
+
return isAgentKind(kind) ? `${AGENTS[kind].factory}({ … })` : "an object";
|
|
476
|
+
};
|
|
477
|
+
function checkProfile(name, profile, file) {
|
|
478
|
+
const where = `profiles.${name}`;
|
|
479
|
+
if (typeof profile !== "object" || profile === null) throw new UsageError(`${where} in ${file} must be an object of profile settings, e.g. { limit: { runs: 1 } } (got ${JSON.stringify(profile)})`);
|
|
480
|
+
unknownKey(profile, PROFILE_KEYS, where, file);
|
|
481
|
+
const p = profile;
|
|
482
|
+
if (p.defaults !== void 0) {
|
|
483
|
+
unknownKey(p.defaults, DEFAULTS_KEYS, `${where}.defaults`, file);
|
|
484
|
+
checkOptions(p.defaults, `${where}.defaults in ${file}`);
|
|
485
|
+
}
|
|
486
|
+
if (p.limit !== void 0) {
|
|
487
|
+
unknownKey(p.limit, LIMIT_KEYS, `${where}.limit`, file);
|
|
488
|
+
const { runs, compare, harness } = p.limit;
|
|
489
|
+
if (runs !== void 0 && !(Number.isInteger(runs) && runs >= 1)) throw new UsageError(`${where}.limit.runs in ${file} must be a whole number of at least 1 (got ${JSON.stringify(runs)})`);
|
|
490
|
+
if (compare !== void 0 && compare !== false && !(Number.isInteger(compare?.maxVariants) && compare.maxVariants >= 0)) throw new UsageError(`${where}.limit.compare in ${file} must be false or { maxVariants: n }, e.g. { maxVariants: 1 } (got ${JSON.stringify(compare)})`);
|
|
491
|
+
if (harness !== void 0) {
|
|
492
|
+
const bad = Array.isArray(harness) ? harness.find((h) => typeof h !== "string") : harness;
|
|
493
|
+
if (bad !== void 0) throw new UsageError(`${where}.limit.harness in ${file} can only list harness ids to allow (got ${describeValue(bad)}). To add harnesses, use ${where}.defaults.harness.`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
if (p.tags !== void 0 && (!Array.isArray(p.tags) || p.tags.some((t) => typeof t !== "string"))) throw new UsageError(`${where}.tags in ${file} must be a list of tag filters, e.g. tags: ['smoke'] (got ${JSON.stringify(p.tags)})`);
|
|
497
|
+
if (p.record !== void 0 && !RECORD.includes(p.record)) throw new UsageError(`${where}.record in ${file} is ${oneOf(RECORD)} (got ${JSON.stringify(p.record)})`);
|
|
498
|
+
if (p.judge !== void 0) resolveJudge(p.judge, file, `${where}.judge`);
|
|
499
|
+
checkBudget(p.budget, `${where}.budget`, file);
|
|
500
|
+
return p;
|
|
501
|
+
}
|
|
502
|
+
/** `--profile`, then `REEVAL_PROFILE`, then `defaultProfile.ci` in CI (or `.local`), then none. */
|
|
503
|
+
function selectProfile(config, flag, env = process.env) {
|
|
504
|
+
const pick = () => {
|
|
505
|
+
if (flag) return [flag, "--profile"];
|
|
506
|
+
if (env.REEVAL_PROFILE) return [env.REEVAL_PROFILE, "REEVAL_PROFILE"];
|
|
507
|
+
const ci = inCI(env);
|
|
508
|
+
if (ci && config.defaultProfile.ci) return [config.defaultProfile.ci, "auto: CI is set"];
|
|
509
|
+
if (!ci && config.defaultProfile.local) return [config.defaultProfile.local, "default"];
|
|
510
|
+
return null;
|
|
511
|
+
};
|
|
512
|
+
const picked = pick();
|
|
513
|
+
if (!picked) return null;
|
|
514
|
+
const [name, reason] = picked;
|
|
515
|
+
const profile = config.profiles[name];
|
|
516
|
+
if (!profile) {
|
|
517
|
+
const names = Object.keys(config.profiles);
|
|
518
|
+
throw new UsageError(`Unknown profile "${name}". ${names.length ? `Profiles in ${config.file}: ${names.join(", ")}.` : `${config.file} defines no profiles.`}`);
|
|
519
|
+
}
|
|
520
|
+
return {
|
|
521
|
+
name,
|
|
522
|
+
reason,
|
|
523
|
+
defaults: profile.defaults ?? {},
|
|
524
|
+
limit: profile.limit ?? {},
|
|
525
|
+
judge: profile.judge ?? {},
|
|
526
|
+
tags: profile.tags ?? [],
|
|
527
|
+
budget: {
|
|
528
|
+
...config.budget,
|
|
529
|
+
...profile.budget
|
|
530
|
+
},
|
|
531
|
+
...profile.record ? { record: profile.record } : {}
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* The config with its defaults filled in and checked; `root` is its directory, which relative
|
|
536
|
+
* host paths written as data resolve against.
|
|
537
|
+
*/
|
|
538
|
+
function resolveConfig(input, root, file = DEFAULT_CONFIG_FILE) {
|
|
539
|
+
const user = withHostPaths(input, root, file);
|
|
540
|
+
if ("compare" in user) throw new UsageError(`compare can't be set in ${file}: it would multiply every eval. Put it on the group or eval that asks the question.`);
|
|
541
|
+
checkOptions(user, file, CONFIG_KEYS);
|
|
542
|
+
const include = user.include ?? defaultIncludeGlobs();
|
|
543
|
+
if (!Array.isArray(include) || include.some((p) => typeof p !== "string")) throw new UsageError(`include in ${file} must be a list of glob patterns, e.g. include: ['evals/**/*.eval.ts'] (got ${JSON.stringify(include)})`);
|
|
544
|
+
const record = user.record ?? "ci";
|
|
545
|
+
if (!RECORD.includes(record)) throw new UsageError(`record in ${file} is ${oneOf(RECORD)} (got ${JSON.stringify(record)})`);
|
|
546
|
+
const artifacts = user.history?.artifacts ?? "all";
|
|
547
|
+
if (!ARTIFACTS.includes(artifacts)) throw new UsageError(`history.artifacts in ${file} is ${oneOf(ARTIFACTS)} (got ${JSON.stringify(artifacts)})`);
|
|
548
|
+
const historyDir = resolve(root, user.history?.dir ?? join(evalRoot(include), ".history"));
|
|
549
|
+
const harness = user.harness === void 0 ? [DEFAULT_HARNESS] : [user.harness].flat();
|
|
550
|
+
if (harness.length === 0) throw new UsageError(`harness is an empty list in ${file}. Give at least one, e.g. harness: ${HARNESS_EXAMPLE}`);
|
|
551
|
+
checkHarnesses(harness, file);
|
|
552
|
+
const profiles = {};
|
|
553
|
+
if (user.profiles !== void 0) {
|
|
554
|
+
if (typeof user.profiles !== "object" || user.profiles === null) throw new UsageError(`profiles in ${file} must be an object of named profiles, e.g. profiles: { pr: { limit: { runs: 1 } } } (got ${JSON.stringify(user.profiles)})`);
|
|
555
|
+
for (const [name, profile] of Object.entries(user.profiles)) profiles[name] = checkProfile(name, profile, file);
|
|
556
|
+
}
|
|
557
|
+
const defaultProfile = user.defaultProfile ?? {};
|
|
558
|
+
unknownKey(defaultProfile, ["ci", "local"], "defaultProfile", file);
|
|
559
|
+
for (const key of ["ci", "local"]) {
|
|
560
|
+
const name = defaultProfile[key];
|
|
561
|
+
if (name !== void 0 && !profiles[name]) {
|
|
562
|
+
const names = Object.keys(profiles);
|
|
563
|
+
throw new UsageError(`defaultProfile.${key} in ${file} names "${name}", which isn't a profile (${names.length ? `profiles: ${names.join(", ")}` : "it defines none"}). Add it under profiles, or name one that's there.`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return {
|
|
567
|
+
root,
|
|
568
|
+
file,
|
|
569
|
+
optionKeys: Object.keys(user),
|
|
570
|
+
include,
|
|
571
|
+
harness,
|
|
572
|
+
sandbox: user.sandbox ?? docker(),
|
|
573
|
+
environment: user.environment,
|
|
574
|
+
runs: user.runs ?? 1,
|
|
575
|
+
gate: user.gate,
|
|
576
|
+
env: user.env ?? {},
|
|
577
|
+
...user.egress !== void 0 ? { egress: user.egress } : {},
|
|
578
|
+
budget: checkBudget(user.budget, "budget", file),
|
|
579
|
+
profiles,
|
|
580
|
+
defaultProfile,
|
|
581
|
+
judge: resolveJudge(user.judge, file),
|
|
582
|
+
capture: user.capture ?? {},
|
|
583
|
+
question: user.question ?? {},
|
|
584
|
+
timeoutMs: parseDuration(user.timeout ?? "15m"),
|
|
585
|
+
concurrency: user.concurrency === void 0 ? void 0 : checkConcurrency(user.concurrency, `concurrency in ${file}`),
|
|
586
|
+
record,
|
|
587
|
+
history: {
|
|
588
|
+
dir: historyDir,
|
|
589
|
+
artifacts
|
|
590
|
+
},
|
|
591
|
+
secrets: checkSecrets(user.secrets, file),
|
|
592
|
+
debug: checkDebug(user.debug, file),
|
|
593
|
+
analysis: checkAnalysis(user.analysis, file)
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
const ANALYSIS_DEFAULTS = {
|
|
597
|
+
maxInputTokens: 2e4,
|
|
598
|
+
maxOutputTokens: 8e3
|
|
599
|
+
};
|
|
600
|
+
function checkAnalysis(analysis, file) {
|
|
601
|
+
if (analysis === void 0) return { ...ANALYSIS_DEFAULTS };
|
|
602
|
+
if (typeof analysis !== "object" || analysis === null) throw new UsageError(`analysis in ${file} must be an object like { model: 'google/gemini-3.8-flash' } (got ${JSON.stringify(analysis)})`);
|
|
603
|
+
unknownKey(analysis, [
|
|
604
|
+
"model",
|
|
605
|
+
"agent",
|
|
606
|
+
"instructions",
|
|
607
|
+
"maxInputTokens",
|
|
608
|
+
"maxOutputTokens"
|
|
609
|
+
], "analysis", file);
|
|
610
|
+
const { model, agent, instructions, maxInputTokens = ANALYSIS_DEFAULTS.maxInputTokens, maxOutputTokens = ANALYSIS_DEFAULTS.maxOutputTokens } = analysis;
|
|
611
|
+
if (typeof model === "string") {
|
|
612
|
+
const problem = checkModelString(model, `analysis.model in ${file}`, [`google/${model}`]);
|
|
613
|
+
if (problem) throw new UsageError(problem);
|
|
614
|
+
} else if (model !== void 0 && !(isObjectLike(model) && model.kind === "fake-analysis")) throw new UsageError(`analysis.model in ${file} must be a 'provider/model' string, e.g. 'google/gemini-3.8-flash' (got ${JSON.stringify(model)})`);
|
|
615
|
+
if (agent !== void 0 && typeof agent !== "boolean") throw new UsageError(`analysis.agent in ${file} must be true or false (got ${JSON.stringify(agent)})`);
|
|
616
|
+
if (instructions !== void 0 && typeof instructions !== "string") throw new UsageError(`analysis.instructions in ${file} must be a string (got ${JSON.stringify(instructions)})`);
|
|
617
|
+
if (!(Number.isInteger(maxInputTokens) && maxInputTokens >= 1e3)) throw new UsageError(`analysis.maxInputTokens in ${file} must be a whole number of at least 1000 (got ${maxInputTokens})`);
|
|
618
|
+
if (!(Number.isInteger(maxOutputTokens) && maxOutputTokens >= 500)) throw new UsageError(`analysis.maxOutputTokens in ${file} must be a whole number of at least 500 (got ${maxOutputTokens})`);
|
|
619
|
+
return {
|
|
620
|
+
...analysis,
|
|
621
|
+
maxInputTokens,
|
|
622
|
+
maxOutputTokens
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
/** Runs whose local debug data `.reeval/debug` keeps, and the trace buffer per attempt. */
|
|
626
|
+
const DEBUG_DEFAULTS = {
|
|
627
|
+
keep: 20,
|
|
628
|
+
traceBufferMb: 8
|
|
629
|
+
};
|
|
630
|
+
function checkDebug(debug, file) {
|
|
631
|
+
if (debug === void 0) return {
|
|
632
|
+
keep: DEBUG_DEFAULTS.keep,
|
|
633
|
+
traceBufferBytes: DEBUG_DEFAULTS.traceBufferMb * 2 ** 20
|
|
634
|
+
};
|
|
635
|
+
if (typeof debug !== "object" || debug === null) throw new UsageError(`debug in ${file} must be an object like { keep: 20, traceBufferMb: 8 } (got ${JSON.stringify(debug)})`);
|
|
636
|
+
unknownKey(debug, ["keep", "traceBufferMb"], "debug", file);
|
|
637
|
+
const { keep = DEBUG_DEFAULTS.keep, traceBufferMb = DEBUG_DEFAULTS.traceBufferMb } = debug;
|
|
638
|
+
if (!(Number.isInteger(keep) && keep >= 1)) throw new UsageError(`debug.keep in ${file} must be a whole number of at least 1 (got ${JSON.stringify(keep)})`);
|
|
639
|
+
if (!(typeof traceBufferMb === "number" && traceBufferMb > 0)) throw new UsageError(`debug.traceBufferMb in ${file} must be a number of megabytes above 0 (got ${JSON.stringify(traceBufferMb)})`);
|
|
640
|
+
return {
|
|
641
|
+
keep,
|
|
642
|
+
traceBufferBytes: Math.round(traceBufferMb * 2 ** 20)
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
function checkSecrets(secrets, file) {
|
|
646
|
+
if (secrets === void 0) return { unbrokered: "warn" };
|
|
647
|
+
if (typeof secrets !== "object" || secrets === null) throw new UsageError(`secrets in ${file} must be an object like { unbrokered: 'error' } (got ${JSON.stringify(secrets)})`);
|
|
648
|
+
unknownKey(secrets, ["unbrokered"], "secrets", file);
|
|
649
|
+
const unbrokered = secrets.unbrokered ?? "warn";
|
|
650
|
+
if (unbrokered !== "warn" && unbrokered !== "error") throw new UsageError(`secrets.unbrokered in ${file} is 'warn' or 'error' (got ${JSON.stringify(unbrokered)})`);
|
|
651
|
+
return { unbrokered };
|
|
652
|
+
}
|
|
653
|
+
const hostCapacity = () => ({
|
|
654
|
+
cpus: availableParallelism(),
|
|
655
|
+
memoryBytes: totalmem()
|
|
656
|
+
});
|
|
657
|
+
/**
|
|
658
|
+
* Attempts mostly wait on a model, so the default is 8, but no more than one per CPU or per GB
|
|
659
|
+
* of memory where the sandboxes run (the Docker VM, under Docker Desktop): an idle agent
|
|
660
|
+
* container takes about 300 MB, and the rest is headroom for what the agent runs.
|
|
661
|
+
*/
|
|
662
|
+
function defaultConcurrency({ cpus, memoryBytes }) {
|
|
663
|
+
return Math.max(1, Math.min(8, cpus, Math.floor(memoryBytes / 1e9)));
|
|
664
|
+
}
|
|
665
|
+
function checkConcurrency(value, where) {
|
|
666
|
+
const n = typeof value === "string" && value.trim() !== "" ? Number(value) : value;
|
|
667
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 1) throw new UsageError(`${where} must be a whole number of at least 1 (got ${String(value)})`);
|
|
668
|
+
return n;
|
|
669
|
+
}
|
|
670
|
+
/** `--concurrency`, then `REEVAL_CONCURRENCY`, then `concurrency` in the config, then the default. */
|
|
671
|
+
async function resolveConcurrency(config, flag, capacity, env = process.env) {
|
|
672
|
+
if (flag !== void 0) return checkConcurrency(flag, "--concurrency");
|
|
673
|
+
if (env.REEVAL_CONCURRENCY) return checkConcurrency(env.REEVAL_CONCURRENCY, "REEVAL_CONCURRENCY");
|
|
674
|
+
return config.concurrency ?? defaultConcurrency(await capacity());
|
|
675
|
+
}
|
|
676
|
+
function resolveJudge(judge, file, path = "judge") {
|
|
677
|
+
judge ??= {};
|
|
678
|
+
const problem = judgeProblem(judge, {
|
|
679
|
+
at: "config",
|
|
680
|
+
path,
|
|
681
|
+
file
|
|
682
|
+
});
|
|
683
|
+
if (problem) throw new UsageError(problem);
|
|
684
|
+
const threshold = judge.threshold ?? .8;
|
|
685
|
+
return {
|
|
686
|
+
...judge,
|
|
687
|
+
threshold
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
/** Host variables that required `env` secrets read, as `[sandbox name, host name]`. */
|
|
691
|
+
function secretSources(env) {
|
|
692
|
+
return Object.entries(env).flatMap(([name, value]) => isSecret(value) && !value.optional ? [[name, value.from ?? name]] : []);
|
|
693
|
+
}
|
|
694
|
+
/** The provider model `analysis.agent` calls during a run, if it's on: `analysis.model`, else the judge's. */
|
|
695
|
+
function agentModel(config) {
|
|
696
|
+
if (!config.analysis.agent) return null;
|
|
697
|
+
const model = config.analysis.model ?? config.judge.model;
|
|
698
|
+
return typeof model === "string" && !model.startsWith("typesafe-ai/") ? model : null;
|
|
699
|
+
}
|
|
700
|
+
/** The run's judge model: the profile's when it sets one, else the config's. */
|
|
701
|
+
function runJudgeModel(config, profile) {
|
|
702
|
+
return profile ? profileJudge(profile).model ?? config.judge.model : config.judge.model;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Names the environment variables the run's providers need but don't have: the harnesses it
|
|
706
|
+
* plans (default: the config's), the run's judge, the models that answer compare questions,
|
|
707
|
+
* the analysis model with `analysis.agent`, and required config and profile secrets.
|
|
708
|
+
*/
|
|
709
|
+
function missingCredentials(config, env = process.env, options = {}) {
|
|
710
|
+
const missing = [];
|
|
711
|
+
const keys = new Set((options.harnesses ?? config.harness).flatMap((h) => credentialOf(h) ?? []));
|
|
712
|
+
for (const key of keys) if (!env[key]) missing.push(key);
|
|
713
|
+
const judge = runJudgeModel(config, options.profile);
|
|
714
|
+
const agent = agentModel(config);
|
|
715
|
+
const models = [
|
|
716
|
+
...judge !== void 0 ? [judge] : [],
|
|
717
|
+
...options.questionModels ?? [],
|
|
718
|
+
...agent ? [agent] : []
|
|
719
|
+
];
|
|
720
|
+
for (const model of models) {
|
|
721
|
+
const modelKeys = judgeCredentials(model);
|
|
722
|
+
const alternatives = modelKeys.join(" or ");
|
|
723
|
+
if (modelKeys.length && !modelKeys.some((k) => env[k]) && !missing.includes(alternatives)) missing.push(alternatives);
|
|
724
|
+
}
|
|
725
|
+
const vars = {
|
|
726
|
+
...config.env,
|
|
727
|
+
...options.profile?.defaults.env
|
|
728
|
+
};
|
|
729
|
+
for (const [name, from] of secretSources(vars)) if (!env[from]) missing.push(from === name ? name : `${from} (for ${name})`);
|
|
730
|
+
return missing;
|
|
731
|
+
}
|
|
732
|
+
/** Where a value has its first whitespace or control character, from 1; 0 when it has none. */
|
|
733
|
+
const badCharacterAt = (value) => [...value].findIndex((c) => /[\s\p{Cc}]/u.test(c)) + 1;
|
|
734
|
+
/**
|
|
735
|
+
* Keys the run sends in request headers (the agents' credentials, the run's judge's and the
|
|
736
|
+
* compare questions' models') whose value has
|
|
737
|
+
* whitespace or a control character in it, with where the first one is: a key pasted from a
|
|
738
|
+
* wrapped terminal line picks up a line break, and the agent then hangs on a request it can't send
|
|
739
|
+
* rather than failing to authenticate.
|
|
740
|
+
*/
|
|
741
|
+
function malformedCredentials(config, env = process.env, options = {}) {
|
|
742
|
+
const names = (options.harnesses ?? config.harness).flatMap((h) => credentialOf(h) ?? []);
|
|
743
|
+
const judge = runJudgeModel(config, options.profile);
|
|
744
|
+
if (judge !== void 0) names.push(...judgeCredentials(judge));
|
|
745
|
+
for (const model of options.questionModels ?? []) names.push(...judgeCredentials(model));
|
|
746
|
+
const analyst = agentModel(config);
|
|
747
|
+
if (analyst) names.push(...judgeCredentials(analyst));
|
|
748
|
+
return [...new Set(names)].flatMap((name) => {
|
|
749
|
+
const at = badCharacterAt(env[name] ?? "");
|
|
750
|
+
return at ? [`${name} (character ${at})`] : [];
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
//#endregion
|
|
754
|
+
export { configPathFor as a, findConfig as c, malformedCredentials as d, missingCredentials as f, selectProfile as h, configLabel as i, hostCapacity as l, resolveConcurrency as m, badCharacterAt as n, defaultConcurrency as o, requireConfig as p, configFilesPhrase as r, defineConfig as s, CONFIG_FILES as t, loadConfig as u };
|
|
755
|
+
|
|
756
|
+
//# sourceMappingURL=config-DEgnUiU-.js.map
|