@runcofactor/cli 0.52.0 → 0.53.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 +2 -0
- package/dist/{account-3ZG24MOE.js → account-MRZ54P5P.js} +5 -5
- package/dist/{alert-5G7PTAX2.js → alert-XBHELHEI.js} +8 -8
- package/dist/{api-catalog-IBYAFKKQ.js → api-catalog-GWYRX53L.js} +5 -5
- package/dist/{api-key-UXC44BDR.js → api-key-FH6JN447.js} +5 -5
- package/dist/{attachment-T6IRFQW3.js → attachment-J7GRVT5F.js} +5 -5
- package/dist/{auth-SHOCJE6Z.js → auth-VZZQXRDR.js} +3 -3
- package/dist/{benchmark-NJ2U6VTC.js → benchmark-A3SNDARF.js} +5 -5
- package/dist/{blackcat-RQJVFGXQ.js → blackcat-UM6TBQBN.js} +3 -3
- package/dist/{chunk-DHAGRM2F.js → chunk-33U56YGD.js} +14 -5
- package/dist/{chunk-DHAGRM2F.js.map → chunk-33U56YGD.js.map} +1 -1
- package/dist/{chunk-M752EUK3.js → chunk-3JU4UYYN.js} +5 -5
- package/dist/{chunk-G6BCST3G.js → chunk-3ZOUNCA5.js} +7 -7
- package/dist/{chunk-76Z6E7XX.js → chunk-4UZN6TVT.js} +74 -131
- package/dist/chunk-4UZN6TVT.js.map +1 -0
- package/dist/{chunk-ABT3BLRC.js → chunk-5TKXHS3X.js} +3 -3
- package/dist/{chunk-QCYSONXI.js → chunk-CRYHWO4M.js} +2 -2
- package/dist/{chunk-QTYUM4ZB.js → chunk-EJ3ZPH5U.js} +23 -187
- package/dist/chunk-EJ3ZPH5U.js.map +1 -0
- package/dist/{chunk-I5RHTCPY.js → chunk-EL6KJ4PO.js} +4 -4
- package/dist/{chunk-ZL4O6C42.js → chunk-HXBK3AGD.js} +2 -2
- package/dist/{chunk-PRERH4DN.js → chunk-I2SOHQ2C.js} +3 -3
- package/dist/{chunk-CYSPCGO6.js → chunk-JEHHEMUL.js} +5 -5
- package/dist/{chunk-LIEADTEF.js → chunk-LJFVRJCA.js} +2 -2
- package/dist/{chunk-UEJ2HTKU.js → chunk-MJVUMVLO.js} +19 -1
- package/dist/chunk-MJVUMVLO.js.map +1 -0
- package/dist/chunk-N55RKCGR.js +24 -0
- package/dist/chunk-N55RKCGR.js.map +1 -0
- package/dist/{chunk-PHNH3CEU.js → chunk-O4Z44BR7.js} +5 -5
- package/dist/{chunk-QA37WVF6.js → chunk-OQFCZERZ.js} +6 -6
- package/dist/{chunk-EUVB6Y3C.js → chunk-P7L5KELH.js} +8 -8
- package/dist/chunk-P7L5KELH.js.map +1 -0
- package/dist/{chunk-Z3MDQSZG.js → chunk-PJFFGQSB.js} +2 -2
- package/dist/{chunk-IY3ZK46I.js → chunk-QOVJUSSL.js} +2 -2
- package/dist/{chunk-RPJMQBB5.js → chunk-QXU4G4YN.js} +3 -3
- package/dist/{chunk-SZSTJIYU.js → chunk-RRSUKARV.js} +4 -4
- package/dist/{chunk-775FI436.js → chunk-RVD6J6IZ.js} +2 -2
- package/dist/{chunk-5UUIBY6E.js → chunk-TAVBFLCR.js} +3 -3
- package/dist/{chunk-ROX7N3RK.js → chunk-V7HB5LAU.js} +10 -7
- package/dist/chunk-V7HB5LAU.js.map +1 -0
- package/dist/{chunk-LL6TSQWV.js → chunk-VKZOHJCE.js} +3 -3
- package/dist/chunk-VKZOHJCE.js.map +1 -0
- package/dist/{chunk-3HI6QC4V.js → chunk-WRBU6TX7.js} +2 -2
- package/dist/{proofs-TXJGAP5F.js → chunk-X7SUSIY3.js} +118 -20
- package/dist/chunk-X7SUSIY3.js.map +1 -0
- package/dist/{chunk-BF3IPGMP.js → chunk-XSSZMG65.js} +4 -4
- package/dist/{credential-TWSWV64F.js → credential-4RFRU6OH.js} +5 -5
- package/dist/{credential-template-XO5D5AI3.js → credential-template-36PFUTBN.js} +5 -5
- package/dist/{digest-SLEFOLPV.js → digest-THMKFMP7.js} +8 -8
- package/dist/{docs-IDC4V4WQ.js → docs-ZGM53L67.js} +5 -5
- package/dist/{doctor-SDAYEA6C.js → doctor-Z2XBGBM3.js} +3 -3
- package/dist/{environment-VBUIBQ6O.js → environment-5YVZXBN7.js} +7 -7
- package/dist/eval-SO5TPD37.js +1177 -0
- package/dist/eval-SO5TPD37.js.map +1 -0
- package/dist/{exploration-IREIR5PO.js → exploration-GZ4WUJJH.js} +5 -5
- package/dist/{exploratory-test-4Q7BMEFT.js → exploratory-test-Y7P4ZDLD.js} +5 -5
- package/dist/{external-run-S7LULKPT.js → external-run-IJGGZOGI.js} +7 -7
- package/dist/{feature-flag-IASHTUGU.js → feature-flag-UH6Z7774.js} +5 -5
- package/dist/{flow-IBEZFZTU.js → flow-MRWAQYZZ.js} +5 -5
- package/dist/{github-HAEZRULP.js → github-VDJU3PU3.js} +6 -6
- package/dist/index.js +101 -82
- package/dist/index.js.map +1 -1
- package/dist/{init-LT2TZMQY.js → init-CROSTPA6.js} +5 -5
- package/dist/{install-RTEYH6TK.js → install-ECR7OLAY.js} +5 -5
- package/dist/{issues-5GE277R2.js → issues-K5TL2S4Z.js} +7 -7
- package/dist/{knobs-AMIWGYUZ.js → knobs-OKQFBQLK.js} +5 -5
- package/dist/{landing-3VLVVQL5.js → landing-XANIBPVM.js} +7 -7
- package/dist/{list-GDJR6UY6.js → list-64GX47Q4.js} +5 -5
- package/dist/{local-4R6GVW7U.js → local-QMQY2ZUX.js} +7 -7
- package/dist/{login-IXVAWTLO.js → login-I7UQ3GTM.js} +5 -5
- package/dist/{mcp-BM2MJWX4.js → mcp-XZV63DX5.js} +11 -11
- package/dist/{mcp-BM2MJWX4.js.map → mcp-XZV63DX5.js.map} +1 -1
- package/dist/{metrics-SSIP5CEB.js → metrics-KBTQ3KGZ.js} +5 -5
- package/dist/{monitor-ESZYHXT2.js → monitor-UGWY43IU.js} +5 -5
- package/dist/perf-CCHWHCSR.js +961 -0
- package/dist/perf-CCHWHCSR.js.map +1 -0
- package/dist/{product-VCGUV5TH.js → product-KLG2ZTHC.js} +5 -5
- package/dist/{product-map-AXKZNQDM.js → product-map-AMEZ76EQ.js} +5 -5
- package/dist/{prompts-FVPSON2N.js → prompts-ZVQNGL7X.js} +5 -5
- package/dist/proofs-QCNSQAPX.js +20 -0
- package/dist/{property-XEPCC7KQ.js → property-PJDQFHAL.js} +6 -6
- package/dist/{psql-JKF3OYYJ.js → psql-AURAN27C.js} +5 -5
- package/dist/{record-REQ37S54.js → record-SYWBTUOB.js} +6 -6
- package/dist/{redis-WGHT2GJB.js → redis-2H752SKO.js} +5 -5
- package/dist/{release-SNDIVIA6.js → release-OSIYUXSC.js} +5 -5
- package/dist/{runbook-XFJG2LFC.js → runbook-7LVHF6KV.js} +5 -5
- package/dist/{sandbox-DYCKLCMY.js → sandbox-TJQPMRGS.js} +18 -30
- package/dist/sandbox-TJQPMRGS.js.map +1 -0
- package/dist/{scenarios-PVHCKP55.js → scenarios-P2ABU4HK.js} +5 -5
- package/dist/{schedule-DGC7CX23.js → schedule-XLS56NP5.js} +8 -8
- package/dist/{session-ERGUFDDI.js → session-ACSFC3UB.js} +8 -8
- package/dist/{skill-6QO2E3TB.js → skill-F7I5P6AI.js} +43 -28
- package/dist/{skill-6QO2E3TB.js.map → skill-F7I5P6AI.js.map} +1 -1
- package/dist/{slack-WE7THLLH.js → slack-ZEK4GRND.js} +6 -6
- package/dist/{spec-SEOY7N3L.js → spec-XPIRRSV6.js} +5 -5
- package/dist/{src-CCK3TKBX.js → src-N7ZCYCXU.js} +4 -4
- package/dist/{src-AEWV5HYH.js → src-XSBBMVGW.js} +3 -3
- package/dist/{start-EPGLHTND.js → start-DKWLOQI6.js} +5 -5
- package/dist/{status-DTE2GXM2.js → status-K7KH5DI3.js} +5 -5
- package/dist/{tag-GIFEWGBL.js → tag-2Y3OMQQC.js} +6 -6
- package/dist/{task-VLJCHSFB.js → task-4PIOC4QV.js} +6 -6
- package/dist/{teams-J2OOJBL4.js → teams-KUM56BE2.js} +83 -47
- package/dist/teams-KUM56BE2.js.map +1 -0
- package/dist/{test-suite-CTACGIAZ.js → test-suite-SW25YGJV.js} +138 -96
- package/dist/test-suite-SW25YGJV.js.map +1 -0
- package/dist/{trace-RWQDYL4K.js → trace-QPXHYBLA.js} +7 -7
- package/dist/{triage-6UAXLCGU.js → triage-EZABRZZM.js} +6 -6
- package/dist/trigger-7C7KX5KH.js +137 -0
- package/dist/trigger-7C7KX5KH.js.map +1 -0
- package/dist/{update-check-QRREAM3N.js → update-check-E3X7UIY6.js} +3 -3
- package/dist/{upgrade-5Y655TQ6.js → upgrade-EJGTMW44.js} +4 -4
- package/dist/{usage-KCXFY46V.js → usage-GZCQDULF.js} +7 -7
- package/dist/{utilities-SUON2KZK.js → utilities-RIITD53A.js} +6 -6
- package/dist/{visual-GJZVJC32.js → visual-FH67OKS2.js} +5 -5
- package/dist/{workflow-R5QAXWBX.js → workflow-34YJZ2QQ.js} +38 -86
- package/dist/workflow-34YJZ2QQ.js.map +1 -0
- package/dist/{workflow-cache-PFBTWLGM.js → workflow-cache-YQ3BGJIG.js} +17 -17
- package/dist/{workflow-cache-convergence-resolve-UZGRPC3B.js → workflow-cache-convergence-resolve-SPLBRSNK.js} +5 -5
- package/dist/{workflow-debug-RACNWWOL.js → workflow-debug-HZB7TJGK.js} +10 -10
- package/dist/workflow-debug-HZB7TJGK.js.map +1 -0
- package/dist/{workflow-feedback-RJIQ5R3R.js → workflow-feedback-6UOTIWIQ.js} +5 -5
- package/dist/{workflow-fork-LINDCYJ3.js → workflow-fork-IDYHAHCX.js} +5 -5
- package/dist/workflow-promotion-U6WYFY5U.js +62 -0
- package/dist/workflow-promotion-U6WYFY5U.js.map +1 -0
- package/dist/{workflow-proposals-E4EXKAGM.js → workflow-proposals-MMVNIQT3.js} +5 -5
- package/dist/{workflow-revisions-5M6APN6R.js → workflow-revisions-BPUOLCZB.js} +5 -5
- package/dist/{workflow-status-7KL73WS2.js → workflow-status-RA3IEMEW.js} +5 -5
- package/package.json +3 -2
- package/dist/chat-target-IZ7YLLU3.js +0 -502
- package/dist/chat-target-IZ7YLLU3.js.map +0 -1
- package/dist/chunk-76Z6E7XX.js.map +0 -1
- package/dist/chunk-EUVB6Y3C.js.map +0 -1
- package/dist/chunk-LL6TSQWV.js.map +0 -1
- package/dist/chunk-QTYUM4ZB.js.map +0 -1
- package/dist/chunk-ROX7N3RK.js.map +0 -1
- package/dist/chunk-UEJ2HTKU.js.map +0 -1
- package/dist/proofs-TXJGAP5F.js.map +0 -1
- package/dist/sandbox-DYCKLCMY.js.map +0 -1
- package/dist/teams-J2OOJBL4.js.map +0 -1
- package/dist/test-suite-CTACGIAZ.js.map +0 -1
- package/dist/workflow-R5QAXWBX.js.map +0 -1
- /package/dist/{account-3ZG24MOE.js.map → account-MRZ54P5P.js.map} +0 -0
- /package/dist/{alert-5G7PTAX2.js.map → alert-XBHELHEI.js.map} +0 -0
- /package/dist/{api-catalog-IBYAFKKQ.js.map → api-catalog-GWYRX53L.js.map} +0 -0
- /package/dist/{api-key-UXC44BDR.js.map → api-key-FH6JN447.js.map} +0 -0
- /package/dist/{attachment-T6IRFQW3.js.map → attachment-J7GRVT5F.js.map} +0 -0
- /package/dist/{auth-SHOCJE6Z.js.map → auth-VZZQXRDR.js.map} +0 -0
- /package/dist/{benchmark-NJ2U6VTC.js.map → benchmark-A3SNDARF.js.map} +0 -0
- /package/dist/{blackcat-RQJVFGXQ.js.map → blackcat-UM6TBQBN.js.map} +0 -0
- /package/dist/{chunk-M752EUK3.js.map → chunk-3JU4UYYN.js.map} +0 -0
- /package/dist/{chunk-G6BCST3G.js.map → chunk-3ZOUNCA5.js.map} +0 -0
- /package/dist/{chunk-ABT3BLRC.js.map → chunk-5TKXHS3X.js.map} +0 -0
- /package/dist/{chunk-QCYSONXI.js.map → chunk-CRYHWO4M.js.map} +0 -0
- /package/dist/{chunk-I5RHTCPY.js.map → chunk-EL6KJ4PO.js.map} +0 -0
- /package/dist/{chunk-ZL4O6C42.js.map → chunk-HXBK3AGD.js.map} +0 -0
- /package/dist/{chunk-PRERH4DN.js.map → chunk-I2SOHQ2C.js.map} +0 -0
- /package/dist/{chunk-CYSPCGO6.js.map → chunk-JEHHEMUL.js.map} +0 -0
- /package/dist/{chunk-LIEADTEF.js.map → chunk-LJFVRJCA.js.map} +0 -0
- /package/dist/{chunk-PHNH3CEU.js.map → chunk-O4Z44BR7.js.map} +0 -0
- /package/dist/{chunk-QA37WVF6.js.map → chunk-OQFCZERZ.js.map} +0 -0
- /package/dist/{chunk-Z3MDQSZG.js.map → chunk-PJFFGQSB.js.map} +0 -0
- /package/dist/{chunk-IY3ZK46I.js.map → chunk-QOVJUSSL.js.map} +0 -0
- /package/dist/{chunk-RPJMQBB5.js.map → chunk-QXU4G4YN.js.map} +0 -0
- /package/dist/{chunk-SZSTJIYU.js.map → chunk-RRSUKARV.js.map} +0 -0
- /package/dist/{chunk-775FI436.js.map → chunk-RVD6J6IZ.js.map} +0 -0
- /package/dist/{chunk-5UUIBY6E.js.map → chunk-TAVBFLCR.js.map} +0 -0
- /package/dist/{chunk-3HI6QC4V.js.map → chunk-WRBU6TX7.js.map} +0 -0
- /package/dist/{chunk-BF3IPGMP.js.map → chunk-XSSZMG65.js.map} +0 -0
- /package/dist/{credential-TWSWV64F.js.map → credential-4RFRU6OH.js.map} +0 -0
- /package/dist/{credential-template-XO5D5AI3.js.map → credential-template-36PFUTBN.js.map} +0 -0
- /package/dist/{digest-SLEFOLPV.js.map → digest-THMKFMP7.js.map} +0 -0
- /package/dist/{docs-IDC4V4WQ.js.map → docs-ZGM53L67.js.map} +0 -0
- /package/dist/{doctor-SDAYEA6C.js.map → doctor-Z2XBGBM3.js.map} +0 -0
- /package/dist/{environment-VBUIBQ6O.js.map → environment-5YVZXBN7.js.map} +0 -0
- /package/dist/{exploration-IREIR5PO.js.map → exploration-GZ4WUJJH.js.map} +0 -0
- /package/dist/{exploratory-test-4Q7BMEFT.js.map → exploratory-test-Y7P4ZDLD.js.map} +0 -0
- /package/dist/{external-run-S7LULKPT.js.map → external-run-IJGGZOGI.js.map} +0 -0
- /package/dist/{feature-flag-IASHTUGU.js.map → feature-flag-UH6Z7774.js.map} +0 -0
- /package/dist/{flow-IBEZFZTU.js.map → flow-MRWAQYZZ.js.map} +0 -0
- /package/dist/{github-HAEZRULP.js.map → github-VDJU3PU3.js.map} +0 -0
- /package/dist/{init-LT2TZMQY.js.map → init-CROSTPA6.js.map} +0 -0
- /package/dist/{install-RTEYH6TK.js.map → install-ECR7OLAY.js.map} +0 -0
- /package/dist/{issues-5GE277R2.js.map → issues-K5TL2S4Z.js.map} +0 -0
- /package/dist/{knobs-AMIWGYUZ.js.map → knobs-OKQFBQLK.js.map} +0 -0
- /package/dist/{landing-3VLVVQL5.js.map → landing-XANIBPVM.js.map} +0 -0
- /package/dist/{list-GDJR6UY6.js.map → list-64GX47Q4.js.map} +0 -0
- /package/dist/{local-4R6GVW7U.js.map → local-QMQY2ZUX.js.map} +0 -0
- /package/dist/{login-IXVAWTLO.js.map → login-I7UQ3GTM.js.map} +0 -0
- /package/dist/{metrics-SSIP5CEB.js.map → metrics-KBTQ3KGZ.js.map} +0 -0
- /package/dist/{monitor-ESZYHXT2.js.map → monitor-UGWY43IU.js.map} +0 -0
- /package/dist/{product-VCGUV5TH.js.map → product-KLG2ZTHC.js.map} +0 -0
- /package/dist/{product-map-AXKZNQDM.js.map → product-map-AMEZ76EQ.js.map} +0 -0
- /package/dist/{prompts-FVPSON2N.js.map → prompts-ZVQNGL7X.js.map} +0 -0
- /package/dist/{src-AEWV5HYH.js.map → proofs-QCNSQAPX.js.map} +0 -0
- /package/dist/{property-XEPCC7KQ.js.map → property-PJDQFHAL.js.map} +0 -0
- /package/dist/{psql-JKF3OYYJ.js.map → psql-AURAN27C.js.map} +0 -0
- /package/dist/{record-REQ37S54.js.map → record-SYWBTUOB.js.map} +0 -0
- /package/dist/{redis-WGHT2GJB.js.map → redis-2H752SKO.js.map} +0 -0
- /package/dist/{release-SNDIVIA6.js.map → release-OSIYUXSC.js.map} +0 -0
- /package/dist/{runbook-XFJG2LFC.js.map → runbook-7LVHF6KV.js.map} +0 -0
- /package/dist/{scenarios-PVHCKP55.js.map → scenarios-P2ABU4HK.js.map} +0 -0
- /package/dist/{schedule-DGC7CX23.js.map → schedule-XLS56NP5.js.map} +0 -0
- /package/dist/{session-ERGUFDDI.js.map → session-ACSFC3UB.js.map} +0 -0
- /package/dist/{slack-WE7THLLH.js.map → slack-ZEK4GRND.js.map} +0 -0
- /package/dist/{spec-SEOY7N3L.js.map → spec-XPIRRSV6.js.map} +0 -0
- /package/dist/{src-CCK3TKBX.js.map → src-N7ZCYCXU.js.map} +0 -0
- /package/dist/{tag-GIFEWGBL.js.map → src-XSBBMVGW.js.map} +0 -0
- /package/dist/{start-EPGLHTND.js.map → start-DKWLOQI6.js.map} +0 -0
- /package/dist/{status-DTE2GXM2.js.map → status-K7KH5DI3.js.map} +0 -0
- /package/dist/{trace-RWQDYL4K.js.map → tag-2Y3OMQQC.js.map} +0 -0
- /package/dist/{task-VLJCHSFB.js.map → task-4PIOC4QV.js.map} +0 -0
- /package/dist/{triage-6UAXLCGU.js.map → trace-QPXHYBLA.js.map} +0 -0
- /package/dist/{update-check-QRREAM3N.js.map → triage-EZABRZZM.js.map} +0 -0
- /package/dist/{upgrade-5Y655TQ6.js.map → update-check-E3X7UIY6.js.map} +0 -0
- /package/dist/{utilities-SUON2KZK.js.map → upgrade-EJGTMW44.js.map} +0 -0
- /package/dist/{usage-KCXFY46V.js.map → usage-GZCQDULF.js.map} +0 -0
- /package/dist/{workflow-debug-RACNWWOL.js.map → utilities-RIITD53A.js.map} +0 -0
- /package/dist/{visual-GJZVJC32.js.map → visual-FH67OKS2.js.map} +0 -0
- /package/dist/{workflow-cache-PFBTWLGM.js.map → workflow-cache-YQ3BGJIG.js.map} +0 -0
- /package/dist/{workflow-cache-convergence-resolve-UZGRPC3B.js.map → workflow-cache-convergence-resolve-SPLBRSNK.js.map} +0 -0
- /package/dist/{workflow-feedback-RJIQ5R3R.js.map → workflow-feedback-6UOTIWIQ.js.map} +0 -0
- /package/dist/{workflow-fork-LINDCYJ3.js.map → workflow-fork-IDYHAHCX.js.map} +0 -0
- /package/dist/{workflow-proposals-E4EXKAGM.js.map → workflow-proposals-MMVNIQT3.js.map} +0 -0
- /package/dist/{workflow-revisions-5M6APN6R.js.map → workflow-revisions-BPUOLCZB.js.map} +0 -0
- /package/dist/{workflow-status-7KL73WS2.js.map → workflow-status-RA3IEMEW.js.map} +0 -0
|
@@ -0,0 +1,1177 @@
|
|
|
1
|
+
import { createRequire as __cr } from "module"; import { fileURLToPath as __fup } from "url"; import { dirname as __dn } from "path"; const require = __cr(import.meta.url); const __filename = __fup(import.meta.url); const __dirname = __dn(__filename);
|
|
2
|
+
import {
|
|
3
|
+
resolveApplicationId
|
|
4
|
+
} from "./chunk-JEHHEMUL.js";
|
|
5
|
+
import "./chunk-RVD6J6IZ.js";
|
|
6
|
+
import {
|
|
7
|
+
apiRequest,
|
|
8
|
+
getArgValues,
|
|
9
|
+
getPositionalArg,
|
|
10
|
+
getPositionalArgs,
|
|
11
|
+
getScalarArgValue,
|
|
12
|
+
readStdin
|
|
13
|
+
} from "./chunk-VKZOHJCE.js";
|
|
14
|
+
import "./chunk-S3Q7MDCS.js";
|
|
15
|
+
import "./chunk-4UZN6TVT.js";
|
|
16
|
+
import {
|
|
17
|
+
GLOBAL_VALUE_FLAGS,
|
|
18
|
+
apiFetch,
|
|
19
|
+
hasFlag,
|
|
20
|
+
resolveConfig
|
|
21
|
+
} from "./chunk-WRBU6TX7.js";
|
|
22
|
+
import {
|
|
23
|
+
writeRawOutput
|
|
24
|
+
} from "./chunk-MJVUMVLO.js";
|
|
25
|
+
import "./chunk-TOWZAG3I.js";
|
|
26
|
+
|
|
27
|
+
// src/eval.ts
|
|
28
|
+
import { mkdir, readFile as readFile3, readdir as readdir2, writeFile } from "fs/promises";
|
|
29
|
+
import { join as join2 } from "path";
|
|
30
|
+
|
|
31
|
+
// ../types/src/agent-eval-template.ts
|
|
32
|
+
import { z } from "zod";
|
|
33
|
+
var AGENT_EVAL_MAX_BUNDLE_BYTES = 512 * 1024 ** 2;
|
|
34
|
+
var AGENT_EVAL_MAX_TOTAL_BUNDLE_BYTES = 2 * 1024 ** 3;
|
|
35
|
+
var ModelSchema = z.strictObject({
|
|
36
|
+
provider: z.enum(["anthropic", "azure"]),
|
|
37
|
+
model: z.string().min(1)
|
|
38
|
+
});
|
|
39
|
+
var ScriptGrader = z.strictObject({
|
|
40
|
+
id: z.string().regex(/^[a-z0-9-]+$/),
|
|
41
|
+
kind: z.literal("script"),
|
|
42
|
+
command: z.string().min(1)
|
|
43
|
+
});
|
|
44
|
+
var JudgeGrader = z.strictObject({
|
|
45
|
+
id: z.string().regex(/^[a-z0-9-]+$/),
|
|
46
|
+
kind: z.literal("ai-judge"),
|
|
47
|
+
rubric: z.string().min(20),
|
|
48
|
+
scale: z.enum(["binary", "1-5"]),
|
|
49
|
+
passAt: z.number().int().min(2).max(5).optional(),
|
|
50
|
+
// required when scale is '1-5'
|
|
51
|
+
judge: ModelSchema
|
|
52
|
+
});
|
|
53
|
+
var AgentEvalTemplateSchema = z.strictObject({
|
|
54
|
+
version: z.literal(1),
|
|
55
|
+
name: z.string().regex(/^[a-z0-9-]{2,40}$/).refine((n) => n !== "runs" && n !== "compare"),
|
|
56
|
+
// reserved route segments
|
|
57
|
+
description: z.string().min(1).max(500),
|
|
58
|
+
proves: z.strictObject({ capabilities: z.array(z.string().regex(/^CAP-/)).min(1) }),
|
|
59
|
+
// resolved within the application given at push
|
|
60
|
+
runbook: z.string().min(1),
|
|
61
|
+
// path relative to this file
|
|
62
|
+
mode: z.enum(["single-turn", "multi-turn"]).default("single-turn"),
|
|
63
|
+
run: z.string().min(1),
|
|
64
|
+
// shell command, cwd = repo root of the commit; multi-turn: the agent process, JSONL on stdin/stdout
|
|
65
|
+
simulator: ModelSchema.optional(),
|
|
66
|
+
// required when mode is multi-turn: pins the simulated-user model
|
|
67
|
+
input: z.strictObject({
|
|
68
|
+
description: z.string().min(1),
|
|
69
|
+
require: z.array(z.string()).default([])
|
|
70
|
+
}),
|
|
71
|
+
agent: z.strictObject({
|
|
72
|
+
models: z.record(z.string().regex(/^[a-z0-9-]+$/), ModelSchema).refine((m) => Object.keys(m).length >= 1),
|
|
73
|
+
default: z.string()
|
|
74
|
+
// must be a key of models
|
|
75
|
+
}),
|
|
76
|
+
graders: z.array(z.discriminatedUnion("kind", [ScriptGrader, JudgeGrader])).min(1),
|
|
77
|
+
repeats: z.number().int().min(1).max(7).default(3),
|
|
78
|
+
passAtLeast: z.number().int().min(1).default(2),
|
|
79
|
+
// <= repeats
|
|
80
|
+
passBar: z.number().min(0.1).max(1),
|
|
81
|
+
// minimum case pass rate for the eval to pass
|
|
82
|
+
attemptSeconds: z.number().int().min(30).max(1700)
|
|
83
|
+
// grader commands get a fixed 120 s
|
|
84
|
+
}).superRefine((template, ctx) => {
|
|
85
|
+
if (!Object.hasOwn(template.agent.models, template.agent.default)) {
|
|
86
|
+
ctx.addIssue({
|
|
87
|
+
code: "custom",
|
|
88
|
+
path: ["agent", "default"],
|
|
89
|
+
message: "Default must name an agent model alias"
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
if (template.passAtLeast > template.repeats) {
|
|
93
|
+
ctx.addIssue({
|
|
94
|
+
code: "custom",
|
|
95
|
+
path: ["passAtLeast"],
|
|
96
|
+
message: "passAtLeast must not exceed repeats"
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const ids = /* @__PURE__ */ new Set();
|
|
100
|
+
template.graders.forEach((grader, index) => {
|
|
101
|
+
if (ids.has(grader.id)) {
|
|
102
|
+
ctx.addIssue({
|
|
103
|
+
code: "custom",
|
|
104
|
+
path: ["graders", index, "id"],
|
|
105
|
+
message: "Grader ids must be unique"
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
ids.add(grader.id);
|
|
109
|
+
if (grader.kind === "ai-judge" && grader.scale === "1-5" && grader.passAt === void 0) {
|
|
110
|
+
ctx.addIssue({
|
|
111
|
+
code: "custom",
|
|
112
|
+
path: ["graders", index, "passAt"],
|
|
113
|
+
message: "A 1-5 judge requires passAt"
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
if (template.mode === "multi-turn" !== (template.simulator !== void 0)) {
|
|
118
|
+
ctx.addIssue({
|
|
119
|
+
code: "custom",
|
|
120
|
+
path: ["simulator"],
|
|
121
|
+
message: "A simulator is required exactly when mode is multi-turn"
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
var EvalConversationSchema = z.discriminatedUnion("kind", [
|
|
126
|
+
z.strictObject({ kind: z.literal("scripted"), turns: z.array(z.string().min(1)).min(1) }),
|
|
127
|
+
z.strictObject({
|
|
128
|
+
kind: z.literal("simulated"),
|
|
129
|
+
persona: z.string().min(1),
|
|
130
|
+
goal: z.string().min(1),
|
|
131
|
+
facts: z.array(z.string()),
|
|
132
|
+
stopWhen: z.string().min(1),
|
|
133
|
+
maxTurns: z.number().int().min(2).max(30)
|
|
134
|
+
})
|
|
135
|
+
]);
|
|
136
|
+
|
|
137
|
+
// src/eval-api.ts
|
|
138
|
+
var EVALS = "/org/agent-evals";
|
|
139
|
+
var isRecord = (value) => typeof value === "object" && value !== null;
|
|
140
|
+
function describeDetails(details) {
|
|
141
|
+
if (typeof details === "string") return `
|
|
142
|
+
${details}`;
|
|
143
|
+
if (!Array.isArray(details)) return "";
|
|
144
|
+
const lines = details.flatMap((item) => {
|
|
145
|
+
if (typeof item === "string") return [item];
|
|
146
|
+
if (!isRecord(item) || typeof item.message !== "string") return [];
|
|
147
|
+
const path = Array.isArray(item.path) ? item.path.map(String).join(".") : "";
|
|
148
|
+
return [`${path ? `${path}: ` : ""}${item.message}`];
|
|
149
|
+
});
|
|
150
|
+
return lines.length ? `
|
|
151
|
+
${lines.map((line) => ` ${line}`).join("\n")}` : "";
|
|
152
|
+
}
|
|
153
|
+
function describeParams(response) {
|
|
154
|
+
return ["reason", "alias", "capability", "secret"].flatMap((key) => typeof response[key] === "string" ? [`
|
|
155
|
+
${key}: ${response[key]}`] : []).join("");
|
|
156
|
+
}
|
|
157
|
+
function describeFailure(response, method, path) {
|
|
158
|
+
const message = `${response.code ? `${response.code}: ` : ""}${response.error ?? `${method} ${path} failed`}${describeParams(response)}${describeDetails(response.details)}${describeDetails(response.issues)}`;
|
|
159
|
+
return response.code === "API_KEY_INSUFFICIENT_SCOPE" && method === "POST" && path.endsWith("/runs") ? `${message}
|
|
160
|
+
eval run needs an API key with sandbox:create and sandbox:exec; sandbox:* does not grant sandbox:exec.` : message;
|
|
161
|
+
}
|
|
162
|
+
function failureText(failure, method, path) {
|
|
163
|
+
try {
|
|
164
|
+
const parsed = JSON.parse(failure.body ?? "");
|
|
165
|
+
if (isRecord(parsed) && typeof parsed.error === "string")
|
|
166
|
+
return describeFailure(parsed, method, path);
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
return failure.message;
|
|
170
|
+
}
|
|
171
|
+
async function request(apiUrl, token, method, path, body) {
|
|
172
|
+
const response = await apiRequest(apiUrl, token, method, path, body);
|
|
173
|
+
if (!response.ok || response.data === void 0) throw new Error(describeFailure(response, method, path));
|
|
174
|
+
return response.data;
|
|
175
|
+
}
|
|
176
|
+
var authHeaders = (token) => ({ Authorization: `Bearer ${token}`, "User-Agent": "Cofactor-CLI" });
|
|
177
|
+
async function requestText(apiUrl, token, path) {
|
|
178
|
+
const result = await apiFetch(`${apiUrl}${path}`, { headers: authHeaders(token), token });
|
|
179
|
+
if (!result.ok) throw new Error(failureText(result.failure, "GET", path));
|
|
180
|
+
return result.response.text();
|
|
181
|
+
}
|
|
182
|
+
async function requestTolerant(apiUrl, token, path) {
|
|
183
|
+
const result = await apiFetch(`${apiUrl}${path}`, { headers: authHeaders(token), token });
|
|
184
|
+
if (!result.ok) {
|
|
185
|
+
const { failure } = result;
|
|
186
|
+
if (failure.kind === "network" || failure.status !== null && (failure.status >= 500 || failure.status === 429))
|
|
187
|
+
return { transient: failure.message };
|
|
188
|
+
throw new Error(failureText(failure, "GET", path));
|
|
189
|
+
}
|
|
190
|
+
const body = await result.response.json();
|
|
191
|
+
if (!body.ok || body.data === void 0) throw new Error(describeFailure(body, "GET", path));
|
|
192
|
+
return { data: body.data };
|
|
193
|
+
}
|
|
194
|
+
async function requestAll(apiUrl, token, path) {
|
|
195
|
+
const items = [];
|
|
196
|
+
for (let page = 1; ; page++) {
|
|
197
|
+
const response = await apiRequest(
|
|
198
|
+
apiUrl,
|
|
199
|
+
token,
|
|
200
|
+
"GET",
|
|
201
|
+
`${path}?pageSize=100&page=${page}`
|
|
202
|
+
);
|
|
203
|
+
if (!response.ok || !response.data) throw new Error(describeFailure(response, "GET", path));
|
|
204
|
+
items.push(...response.data);
|
|
205
|
+
if (page >= (response.pagination?.totalPages ?? 1)) return items;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
var getEval = (apiUrl, token, name) => request(apiUrl, token, "GET", `${EVALS}/${encodeURIComponent(name)}`);
|
|
209
|
+
|
|
210
|
+
// src/eval-bundle.ts
|
|
211
|
+
import { createHash } from "crypto";
|
|
212
|
+
import { lstat, readdir, readFile } from "fs/promises";
|
|
213
|
+
import { join } from "path";
|
|
214
|
+
import { gunzipSync, gzipSync } from "zlib";
|
|
215
|
+
var BLOCK = 512;
|
|
216
|
+
var MAX_RAW_BYTES = AGENT_EVAL_MAX_BUNDLE_BYTES;
|
|
217
|
+
var MAX_UNPACKED_BYTES = 4 * AGENT_EVAL_MAX_BUNDLE_BYTES;
|
|
218
|
+
var sha256Hex = (data) => createHash("sha256").update(data).digest("hex");
|
|
219
|
+
function octal(value, width) {
|
|
220
|
+
return `${value.toString(8).padStart(width - 1, "0")}\0`;
|
|
221
|
+
}
|
|
222
|
+
function splitName(path) {
|
|
223
|
+
if (Buffer.byteLength(path) <= 100) return { prefix: "", name: path };
|
|
224
|
+
for (let i = path.indexOf("/"); i !== -1; i = path.indexOf("/", i + 1)) {
|
|
225
|
+
const prefix = path.slice(0, i);
|
|
226
|
+
const name = path.slice(i + 1);
|
|
227
|
+
if (Buffer.byteLength(prefix) <= 155 && Buffer.byteLength(name) <= 100) return { prefix, name };
|
|
228
|
+
}
|
|
229
|
+
throw new Error(`Path is too long for a bundle (limit 255 bytes): ${path}`);
|
|
230
|
+
}
|
|
231
|
+
function header(path, size, type, mode) {
|
|
232
|
+
const { prefix, name } = splitName(path);
|
|
233
|
+
const block = Buffer.alloc(BLOCK);
|
|
234
|
+
block.write(name, 0, "utf8");
|
|
235
|
+
block.write(octal(mode, 8), 100, "ascii");
|
|
236
|
+
block.write(octal(0, 8), 108, "ascii");
|
|
237
|
+
block.write(octal(0, 8), 116, "ascii");
|
|
238
|
+
block.write(octal(size, 12), 124, "ascii");
|
|
239
|
+
block.write(octal(0, 12), 136, "ascii");
|
|
240
|
+
block.fill(32, 148, 156);
|
|
241
|
+
block.write(type, 156, "ascii");
|
|
242
|
+
block.write("ustar\0", 257, "ascii");
|
|
243
|
+
block.write("00", 263, "ascii");
|
|
244
|
+
block.write(prefix, 345, "utf8");
|
|
245
|
+
const checksum = block.reduce((sum, byte) => sum + byte, 0);
|
|
246
|
+
block.write(`${checksum.toString(8).padStart(6, "0")}\0 `, 148, "ascii");
|
|
247
|
+
return block;
|
|
248
|
+
}
|
|
249
|
+
async function walk(root, real = "", archive = "") {
|
|
250
|
+
const names = (await readdir(real ? join(root, real) : root)).map((name) => ({ real: name, archive: name.normalize("NFC") })).sort((a, b) => Buffer.compare(Buffer.from(a.archive), Buffer.from(b.archive)));
|
|
251
|
+
const entries = [];
|
|
252
|
+
for (const name of names) {
|
|
253
|
+
const realPath = real ? `${real}/${name.real}` : name.real;
|
|
254
|
+
const path = archive ? `${archive}/${name.archive}` : name.archive;
|
|
255
|
+
const absolute = join(root, realPath);
|
|
256
|
+
const stats = await lstat(absolute);
|
|
257
|
+
if (stats.isDirectory()) {
|
|
258
|
+
entries.push({ path, absolute, directory: true, mode: 493, size: 0 });
|
|
259
|
+
entries.push(...await walk(root, realPath, path));
|
|
260
|
+
} else if (stats.isFile()) {
|
|
261
|
+
entries.push({
|
|
262
|
+
path,
|
|
263
|
+
absolute,
|
|
264
|
+
directory: false,
|
|
265
|
+
mode: stats.mode & 73 ? 493 : 420,
|
|
266
|
+
size: stats.size
|
|
267
|
+
});
|
|
268
|
+
} else {
|
|
269
|
+
throw new Error(
|
|
270
|
+
`Cannot bundle ${path}: only regular files and directories are supported (no symlinks or devices).`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return entries;
|
|
275
|
+
}
|
|
276
|
+
async function tarDirectory(root) {
|
|
277
|
+
const entries = await walk(root);
|
|
278
|
+
const total = entries.reduce((sum, entry) => sum + entry.size, 0);
|
|
279
|
+
if (total > MAX_RAW_BYTES) {
|
|
280
|
+
throw new Error(`The input directory holds ${total} bytes; the CLI bundles at most ${MAX_RAW_BYTES}. Prune it.`);
|
|
281
|
+
}
|
|
282
|
+
const parts = [];
|
|
283
|
+
for (const entry of entries) {
|
|
284
|
+
if (entry.directory) {
|
|
285
|
+
parts.push(header(`${entry.path}/`, 0, "5", entry.mode));
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const content = await readFile(entry.absolute);
|
|
289
|
+
parts.push(header(entry.path, content.length, "0", entry.mode), content);
|
|
290
|
+
const padding = (BLOCK - content.length % BLOCK) % BLOCK;
|
|
291
|
+
if (padding) parts.push(Buffer.alloc(padding));
|
|
292
|
+
}
|
|
293
|
+
parts.push(Buffer.alloc(BLOCK * 2));
|
|
294
|
+
return Buffer.concat(parts);
|
|
295
|
+
}
|
|
296
|
+
function readString(block, start, length) {
|
|
297
|
+
const slice = block.subarray(start, start + length);
|
|
298
|
+
const end = slice.indexOf(0);
|
|
299
|
+
return slice.subarray(0, end === -1 ? length : end).toString("utf8");
|
|
300
|
+
}
|
|
301
|
+
function normalize(path) {
|
|
302
|
+
return path.replace(/^(\.\/)+/, "").replace(/\/+$/, "");
|
|
303
|
+
}
|
|
304
|
+
function listTarEntries(tar) {
|
|
305
|
+
const entries = [];
|
|
306
|
+
let longName = null;
|
|
307
|
+
let offset = 0;
|
|
308
|
+
while (offset + BLOCK <= tar.length) {
|
|
309
|
+
const block = tar.subarray(offset, offset + BLOCK);
|
|
310
|
+
if (block.every((byte) => byte === 0)) break;
|
|
311
|
+
const stored = Number.parseInt(readString(block, 148, 8).trim(), 8);
|
|
312
|
+
const actual = block.reduce((sum, byte, i) => sum + (i >= 148 && i < 156 ? 32 : byte), 0);
|
|
313
|
+
if (stored !== actual) throw new Error("not a valid tar archive (bad header checksum)");
|
|
314
|
+
const size = Number.parseInt(readString(block, 124, 12).trim() || "0", 8);
|
|
315
|
+
const type = String.fromCharCode(block[156] || 48);
|
|
316
|
+
const body = tar.subarray(offset + BLOCK, offset + BLOCK + size);
|
|
317
|
+
offset += BLOCK + Math.ceil(size / BLOCK) * BLOCK;
|
|
318
|
+
if (type === "L") {
|
|
319
|
+
longName = readString(body, 0, body.length);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (type === "x") {
|
|
323
|
+
const record = body.toString("utf8").match(/\d+ path=([^\n]*)\n/);
|
|
324
|
+
if (record?.[1]) longName = record[1];
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (type === "g") continue;
|
|
328
|
+
const prefix = readString(block, 345, 155);
|
|
329
|
+
if (["1", "2", "3", "4", "6"].includes(type))
|
|
330
|
+
throw new Error(`the archive holds a link or device entry (${readString(block, 0, 100)}); only files and directories are supported`);
|
|
331
|
+
const name = longName ?? (prefix ? `${prefix}/${readString(block, 0, 100)}` : readString(block, 0, 100));
|
|
332
|
+
longName = null;
|
|
333
|
+
entries.push(normalize(name));
|
|
334
|
+
}
|
|
335
|
+
return entries;
|
|
336
|
+
}
|
|
337
|
+
async function loadBundle(input) {
|
|
338
|
+
let stats;
|
|
339
|
+
try {
|
|
340
|
+
stats = await lstat(input);
|
|
341
|
+
} catch {
|
|
342
|
+
throw new Error(`--input ${input} does not exist.`);
|
|
343
|
+
}
|
|
344
|
+
let tar;
|
|
345
|
+
if (stats.isDirectory()) {
|
|
346
|
+
tar = await tarDirectory(input);
|
|
347
|
+
} else if (stats.isFile() && /\.(tar\.gz|tgz)$/.test(input)) {
|
|
348
|
+
if (stats.size > AGENT_EVAL_MAX_BUNDLE_BYTES)
|
|
349
|
+
throw new Error(`${input} is ${stats.size} bytes; a bundle is limited to ${AGENT_EVAL_MAX_BUNDLE_BYTES}. Prune it.`);
|
|
350
|
+
const gzip = await readFile(input);
|
|
351
|
+
try {
|
|
352
|
+
tar = gunzipSync(gzip, { maxOutputLength: MAX_UNPACKED_BYTES });
|
|
353
|
+
} catch {
|
|
354
|
+
throw new Error(`--input ${input} is not a valid gzip file, or unpacks to more than ${MAX_UNPACKED_BYTES} bytes.`);
|
|
355
|
+
}
|
|
356
|
+
} else {
|
|
357
|
+
throw new Error("--input must be a directory or a .tar.gz file.");
|
|
358
|
+
}
|
|
359
|
+
const archive = gzipSync(tar, { level: 9 });
|
|
360
|
+
let entries;
|
|
361
|
+
try {
|
|
362
|
+
entries = listTarEntries(tar);
|
|
363
|
+
} catch (error) {
|
|
364
|
+
throw new Error(`--input ${input}: ${error instanceof Error ? error.message : String(error)}.`);
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
sha256: sha256Hex(tar),
|
|
368
|
+
gzipSha256: sha256Hex(archive),
|
|
369
|
+
bytes: archive.length,
|
|
370
|
+
archive,
|
|
371
|
+
entries
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
function missingRequiredPaths(entries, required2) {
|
|
375
|
+
return required2.filter((path) => {
|
|
376
|
+
const wanted = normalize(path);
|
|
377
|
+
return !entries.some((entry) => entry === wanted || entry.startsWith(`${wanted}/`));
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// src/eval-run.ts
|
|
382
|
+
import { setTimeout as sleep } from "timers/promises";
|
|
383
|
+
|
|
384
|
+
// ../types/src/agent-eval-render.ts
|
|
385
|
+
var cell = (value) => value.replaceAll("|", "\\|").replace(/\r?\n/g, " ");
|
|
386
|
+
var percent = (value) => `${Math.round(value * 100)}%`;
|
|
387
|
+
var money = (cents, incomplete = false) => cents === null ? "unpriced" : `$${(cents / 100).toFixed(2)}${incomplete ? "*" : ""}`;
|
|
388
|
+
var minutes = (ms) => (ms / 6e4).toFixed(1);
|
|
389
|
+
var tokenCount = (n) => n >= 1e3 ? `${(n / 1e3).toFixed(n >= 1e5 ? 0 : 1)}k` : `${Math.round(n)}`;
|
|
390
|
+
var tokenPair = (tokens) => `${tokenCount(tokens.input)} / ${tokenCount(tokens.output)}`;
|
|
391
|
+
var meanTaskTokens = (result) => {
|
|
392
|
+
const cases = result.cases;
|
|
393
|
+
const mean = (key) => cases.length ? cases.reduce((n, c) => n + c.tokensPerAttempt[key], 0) / cases.length : 0;
|
|
394
|
+
return {
|
|
395
|
+
input: mean("input"),
|
|
396
|
+
output: mean("output"),
|
|
397
|
+
cacheRead: mean("cacheRead"),
|
|
398
|
+
cacheCreation: mean("cacheCreation")
|
|
399
|
+
};
|
|
400
|
+
};
|
|
401
|
+
var verdict = (r) => (r.verdict ?? r.status).toUpperCase();
|
|
402
|
+
function renderAgentEvalRun(result) {
|
|
403
|
+
const s = result.summary;
|
|
404
|
+
const taskTokens = meanTaskTokens(result);
|
|
405
|
+
const warning = result.warnings.length ? ` [WARNING: ${result.warnings.map(cell).join("; ")}]` : "";
|
|
406
|
+
const multiTurn = s.userSim !== null;
|
|
407
|
+
const turnsToSuccess = (c) => {
|
|
408
|
+
const passing = c.attempts.flatMap((a) => a.passed && a.turns !== null ? [a.turns] : []);
|
|
409
|
+
return passing.length ? (passing.reduce((n, t) => n + t, 0) / passing.length).toFixed(1) : "-";
|
|
410
|
+
};
|
|
411
|
+
const lines = [
|
|
412
|
+
`## Eval ${cell(result.eval)}: ${verdict(result)} (${s.casesPassed}/${s.cases} cases, bar ${percent(result.passBar)}) \u2014 ${cell(result.model.alias)} (${cell(result.model.id)}) @ ${result.commit.slice(0, 7)}, ${result.repeats} repeats${warning}`,
|
|
413
|
+
"",
|
|
414
|
+
`| Case | Result | Attempts | Score | ${multiTurn ? "Turns | " : ""}Cost/attempt | Time/attempt | Tokens in/out |`,
|
|
415
|
+
`|---|---|---|---|${multiTurn ? "---|" : ""}---|---|---|`,
|
|
416
|
+
...result.cases.map(
|
|
417
|
+
(c) => `| ${cell(c.key)} | ${c.passed ? "PASS" : "FAIL"} | ${c.passedAttempts}/${c.attempts.length} | ${c.meanScore.toFixed(1)} | ${multiTurn ? `${turnsToSuccess(c)} | ` : ""}${money(
|
|
418
|
+
c.costCentsPerAttempt,
|
|
419
|
+
c.attempts.some((a) => !a.costComplete)
|
|
420
|
+
)} | ${minutes(c.durationMsPerAttempt)} min | ${tokenPair(c.tokensPerAttempt)} |`
|
|
421
|
+
),
|
|
422
|
+
"",
|
|
423
|
+
`Per task: ${money(s.costCentsPerTask, s.incompleteCalls > 0)} | ${minutes(s.durationMsPerTask)} min | ${tokenCount(taskTokens.input)} in / ${tokenCount(taskTokens.output)} out. Total: ${money(s.costCents, s.incompleteCalls > 0)} for ${s.attempts} attempts, ${Math.round(result.cases.flatMap((c) => c.attempts).reduce((n, a) => n + a.durationMs, 0) / 6e4)} min of agent time.`
|
|
424
|
+
];
|
|
425
|
+
const judges = [
|
|
426
|
+
...new Set(
|
|
427
|
+
result.cases.flatMap((c) => c.attempts.flatMap((a) => a.grades)).filter((g) => g.kind === "ai-judge").map((g) => g.judgeModel)
|
|
428
|
+
)
|
|
429
|
+
];
|
|
430
|
+
lines.push(
|
|
431
|
+
`Judge (${judges.length ? judges.map((m) => cell(m ?? "unknown")).join(", ") : "none"}, pinned): ${s.judge.calls} calls, ${money(s.judge.costCents)}, not included above.`
|
|
432
|
+
);
|
|
433
|
+
if (s.userSim) {
|
|
434
|
+
const models = [
|
|
435
|
+
...new Set(
|
|
436
|
+
result.cases.flatMap(
|
|
437
|
+
(c) => c.attempts.flatMap((a) => a.calls.filter((call) => call.role === "user-sim").map((call) => call.model))
|
|
438
|
+
)
|
|
439
|
+
)
|
|
440
|
+
];
|
|
441
|
+
lines.push(
|
|
442
|
+
`User-sim (${models.length ? models.map(cell).join(", ") : "none"}, pinned): ${s.userSim.calls} calls, ${money(s.userSim.costCents)}, not included above.`
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
const failures = result.cases.flatMap(
|
|
446
|
+
(c) => c.attempts.flatMap((a) => [
|
|
447
|
+
...a.grades.filter((g) => !g.pass).map((g) => `${cell(c.key)} #${a.index}: ${cell(g.grader)}: ${cell(g.reason)}`),
|
|
448
|
+
...a.error && !a.grades.some((g) => !g.pass && (g.reason === a.error || g.error === a.error)) ? [`${cell(c.key)} #${a.index}: ${cell(a.error)}`] : []
|
|
449
|
+
])
|
|
450
|
+
);
|
|
451
|
+
lines.push(`Failed grades: ${failures.length ? failures.join("; ") : "none"}.`);
|
|
452
|
+
if (result.error) lines.push(`Error: ${cell(result.error)}.`);
|
|
453
|
+
if (s.incompleteCalls) lines.push("* Cost is a lower bound because agent calls were incomplete.");
|
|
454
|
+
lines.push(
|
|
455
|
+
`Warnings: ${result.warnings.length ? result.warnings.map(cell).join("; ") : "none"}. Run: ${result.id}.`
|
|
456
|
+
);
|
|
457
|
+
return `${lines.join("\n")}
|
|
458
|
+
`;
|
|
459
|
+
}
|
|
460
|
+
function renderAgentEvalCompare(result) {
|
|
461
|
+
const lines = [
|
|
462
|
+
`## Eval compare: ${cell(result.eval)} @ ${result.commit.slice(0, 7)}${result.batchId ? ` (batch ${result.batchId})` : ""}`,
|
|
463
|
+
"",
|
|
464
|
+
"| Model | Verdict | Pass rate | Mean score | Cost/task | Time/task | Tokens/task in/out |",
|
|
465
|
+
"|---|---|---|---|---|---|---|",
|
|
466
|
+
...result.runs.map(
|
|
467
|
+
(r) => `| ${cell(r.model.alias)} (${cell(r.model.id)}) | ${verdict(r)} | ${percent(r.summary.passRate)} | ${r.summary.meanScore.toFixed(1)} | ${money(r.summary.costCentsPerTask, r.summary.incompleteCalls > 0)} | ${minutes(r.summary.durationMsPerTask)} min | ${tokenPair(meanTaskTokens(r))} |`
|
|
468
|
+
),
|
|
469
|
+
"",
|
|
470
|
+
`| Case | ${result.runs.map((r) => cell(r.model.alias)).join(" | ")} |`,
|
|
471
|
+
`|---|${result.runs.map(() => "---|").join("")}`,
|
|
472
|
+
...result.cases.map(
|
|
473
|
+
(c) => `| ${cell(c.key)} | ${c.models.map(
|
|
474
|
+
({ result: r }) => `${r.passedAttempts}/${r.attempts.length} ${r.passed ? "PASS" : "FAIL"} ${money(
|
|
475
|
+
r.costCentsPerAttempt,
|
|
476
|
+
r.attempts.some((a) => !a.costComplete)
|
|
477
|
+
)} ${minutes(r.durationMsPerAttempt)}m`
|
|
478
|
+
).join(" | ")} |`
|
|
479
|
+
)
|
|
480
|
+
];
|
|
481
|
+
for (const r of result.runs)
|
|
482
|
+
if (r.warnings.length)
|
|
483
|
+
lines.push(`Warnings (${cell(r.model.alias)}): ${r.warnings.map(cell).join("; ")}.`);
|
|
484
|
+
if (result.runs.some((r) => r.summary.incompleteCalls > 0))
|
|
485
|
+
lines.push("* Cost is a lower bound because agent calls were incomplete.");
|
|
486
|
+
return `${lines.join("\n")}
|
|
487
|
+
`;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// src/eval-run.ts
|
|
491
|
+
var pollSettings = { intervalMs: 5e3 };
|
|
492
|
+
var WAIT_MS = 8.5 * 60 * 60 * 1e3;
|
|
493
|
+
var MAX_TRANSIENT_FAILURES = 5;
|
|
494
|
+
var FINISHED = ["completed", "failed", "cancelled"];
|
|
495
|
+
function intFlag(argv, flag) {
|
|
496
|
+
const value = getScalarArgValue(argv, flag);
|
|
497
|
+
if (value === void 0) return void 0;
|
|
498
|
+
if (!/^\d+$/.test(value)) throw new Error(`${flag} must be a whole number.`);
|
|
499
|
+
return Number(value);
|
|
500
|
+
}
|
|
501
|
+
var runPath = (id, transcript = false) => `${EVALS}/runs/${encodeURIComponent(id)}${transcript ? "?transcript=1" : ""}`;
|
|
502
|
+
var describeRun = (run) => `${run.id} ${run.modelAlias} ${run.status}${run.verdict ? ` ${run.verdict}` : ""}${run.error ? ` ${run.error}` : ""}`;
|
|
503
|
+
var stub = (run) => ({ id: run.id, status: run.status, verdict: run.verdict, error: run.error });
|
|
504
|
+
function exitCodeFor(runs) {
|
|
505
|
+
if (runs.some((run) => run.status !== "completed")) return 1;
|
|
506
|
+
return runs.some((run) => run.verdict !== "pass") ? 3 : 0;
|
|
507
|
+
}
|
|
508
|
+
async function waitForRuns(apiUrl, token, evalName, started) {
|
|
509
|
+
const ids = `batch ${started.batchId}; runs ${started.runIds.join(", ")}`;
|
|
510
|
+
let transient = 0;
|
|
511
|
+
const get = async (path) => {
|
|
512
|
+
const reply = await requestTolerant(apiUrl, token, path);
|
|
513
|
+
if ("data" in reply) {
|
|
514
|
+
transient = 0;
|
|
515
|
+
return reply.data;
|
|
516
|
+
}
|
|
517
|
+
if (++transient >= MAX_TRANSIENT_FAILURES)
|
|
518
|
+
throw new Error(`Giving up after ${transient} failed polls (${reply.transient}). Still readable: ${ids}.`);
|
|
519
|
+
return null;
|
|
520
|
+
};
|
|
521
|
+
const deadline = Date.now() + WAIT_MS;
|
|
522
|
+
for (; ; ) {
|
|
523
|
+
if (Date.now() >= deadline)
|
|
524
|
+
throw new Error(`Timed out waiting for the runs. Inspect them with cofactor eval get <run-id>: ${ids}.`);
|
|
525
|
+
const page = await get(`${EVALS}/${encodeURIComponent(evalName)}/runs?limit=100`);
|
|
526
|
+
if (page) {
|
|
527
|
+
const statuses = new Map(page.items.map((item) => [item.id, item.status]));
|
|
528
|
+
const known = await Promise.all(
|
|
529
|
+
started.runIds.map(async (id) => statuses.get(id) ?? (await get(runPath(id)))?.status)
|
|
530
|
+
);
|
|
531
|
+
if (known.every((status) => status !== void 0 && FINISHED.includes(status))) break;
|
|
532
|
+
}
|
|
533
|
+
await sleep(pollSettings.intervalMs);
|
|
534
|
+
}
|
|
535
|
+
const details = [];
|
|
536
|
+
for (const id of started.runIds) {
|
|
537
|
+
let detail = null;
|
|
538
|
+
while (!(detail = await get(runPath(id)))) await sleep(pollSettings.intervalMs);
|
|
539
|
+
details.push(detail);
|
|
540
|
+
}
|
|
541
|
+
return details;
|
|
542
|
+
}
|
|
543
|
+
async function runEval(argv, name, apiUrl, token) {
|
|
544
|
+
const json = hasFlag(argv, "--json");
|
|
545
|
+
const models = getArgValues(argv, "--model");
|
|
546
|
+
const cases = getArgValues(argv, "--case");
|
|
547
|
+
const at = getScalarArgValue(argv, "--at");
|
|
548
|
+
const environment = getScalarArgValue(argv, "--environment");
|
|
549
|
+
const repeats = intFlag(argv, "--repeats");
|
|
550
|
+
const parallel = intFlag(argv, "--parallel");
|
|
551
|
+
const evaluation = await getEval(apiUrl, token, name);
|
|
552
|
+
const started = await request(
|
|
553
|
+
apiUrl,
|
|
554
|
+
token,
|
|
555
|
+
"POST",
|
|
556
|
+
`${EVALS}/${encodeURIComponent(evaluation.id)}/runs`,
|
|
557
|
+
{
|
|
558
|
+
...models.length ? { models } : {},
|
|
559
|
+
...environment ? { environment } : {},
|
|
560
|
+
...at ? { at } : {},
|
|
561
|
+
...cases.length ? { cases } : {},
|
|
562
|
+
...repeats !== void 0 ? { repeats } : {},
|
|
563
|
+
...parallel !== void 0 ? { parallel } : {}
|
|
564
|
+
}
|
|
565
|
+
);
|
|
566
|
+
if (hasFlag(argv, "--no-wait")) {
|
|
567
|
+
writeRawOutput(
|
|
568
|
+
json ? JSON.stringify(started, null, 2) : `Started batch ${started.batchId}
|
|
569
|
+
${started.runIds.join("\n")}
|
|
570
|
+
Read results with: cofactor eval get <run-id> | cofactor eval compare --batch ${started.batchId}`
|
|
571
|
+
);
|
|
572
|
+
return 0;
|
|
573
|
+
}
|
|
574
|
+
console.error(
|
|
575
|
+
`Started batch ${started.batchId}; runs ${started.runIds.join(", ")}. If this is interrupted, read results with cofactor eval get <run-id> or cofactor eval compare --batch ${started.batchId}.`
|
|
576
|
+
);
|
|
577
|
+
const details = await waitForRuns(apiUrl, token, name, started);
|
|
578
|
+
const results = details.flatMap((run) => run.result ? [run.result] : []);
|
|
579
|
+
if (json) {
|
|
580
|
+
writeRawOutput(JSON.stringify(details.map((run) => run.result ?? stub(run)), null, 2));
|
|
581
|
+
} else {
|
|
582
|
+
const compare = details.length > 1 && results.length === details.length ? await request(apiUrl, token, "GET", `${EVALS}/compare?batch=${encodeURIComponent(started.batchId)}`) : null;
|
|
583
|
+
if (results.length)
|
|
584
|
+
writeRawOutput(
|
|
585
|
+
[...results.map(renderAgentEvalRun), ...compare ? [renderAgentEvalCompare(compare)] : []].join("\n").trimEnd()
|
|
586
|
+
);
|
|
587
|
+
for (const run of details.filter((run2) => !run2.result || run2.status !== "completed")) console.error(describeRun(run));
|
|
588
|
+
}
|
|
589
|
+
return exitCodeFor(details);
|
|
590
|
+
}
|
|
591
|
+
async function getRun(argv, target2, apiUrl, token) {
|
|
592
|
+
const json = hasFlag(argv, "--json");
|
|
593
|
+
const caseKey = getScalarArgValue(argv, "--case");
|
|
594
|
+
const transcript = hasFlag(argv, "--transcript");
|
|
595
|
+
if (transcript && !caseKey) throw new Error("--transcript needs --case <key>: transcripts are large, so the CLI prints one case at a time.");
|
|
596
|
+
const run = await request(apiUrl, token, "GET", runPath(target2, transcript));
|
|
597
|
+
if (!run.result) {
|
|
598
|
+
writeRawOutput(json ? JSON.stringify(stub(run), null, 2) : describeRun(run));
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
const result = {
|
|
602
|
+
...run.result,
|
|
603
|
+
cases: run.result.cases.filter((c) => caseKey === void 0 || c.key === caseKey).map((c) => ({
|
|
604
|
+
...c,
|
|
605
|
+
attempts: c.attempts.map((attempt) => ({
|
|
606
|
+
...attempt,
|
|
607
|
+
transcript: transcript ? run.attempts.find((a) => a.case.key === c.key && a.attemptIndex === attempt.index)?.transcript ?? attempt.transcript : attempt.transcript
|
|
608
|
+
}))
|
|
609
|
+
}))
|
|
610
|
+
};
|
|
611
|
+
if (caseKey && result.cases.length === 0) throw new Error(`Run ${target2} has no case "${caseKey}".`);
|
|
612
|
+
if (json) {
|
|
613
|
+
writeRawOutput(JSON.stringify(result, null, 2));
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
const lines = [renderAgentEvalRun(result).trimEnd()];
|
|
617
|
+
if (transcript) {
|
|
618
|
+
for (const attempt of result.cases.flatMap((c) => c.attempts)) {
|
|
619
|
+
lines.push("", `Transcript, attempt ${attempt.index}:`);
|
|
620
|
+
lines.push(
|
|
621
|
+
...attempt.transcript?.length ? attempt.transcript.map((entry) => `${entry.role}: ${entry.text}`) : ["(none: this eval is not multi-turn, or the attempt did not run)"]
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
writeRawOutput(lines.join("\n"));
|
|
626
|
+
}
|
|
627
|
+
async function listRuns(argv, name, apiUrl, token) {
|
|
628
|
+
const limit = intFlag(argv, "--limit");
|
|
629
|
+
const page = await request(
|
|
630
|
+
apiUrl,
|
|
631
|
+
token,
|
|
632
|
+
"GET",
|
|
633
|
+
`${EVALS}/${encodeURIComponent(name)}/runs${limit !== void 0 ? `?limit=${limit}` : ""}`
|
|
634
|
+
);
|
|
635
|
+
writeRawOutput(
|
|
636
|
+
hasFlag(argv, "--json") ? JSON.stringify(page, null, 2) : page.items.map((run) => `${run.id} ${run.modelAlias} ${run.status} ${run.verdict ?? "-"} ${run.commitSha.slice(0, 7)} batch ${run.batchId}`).join("\n") || "No runs found."
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
async function compareRuns(argv, ids, apiUrl, token) {
|
|
640
|
+
const batch = getScalarArgValue(argv, "--batch");
|
|
641
|
+
if (Boolean(batch) === ids.length > 0) throw new Error("Use either one or more run ids or --batch <id>.");
|
|
642
|
+
const query = batch ? `batch=${encodeURIComponent(batch)}` : `runs=${ids.map(encodeURIComponent).join(",")}`;
|
|
643
|
+
const comparison = await request(apiUrl, token, "GET", `${EVALS}/compare?${query}`);
|
|
644
|
+
writeRawOutput(
|
|
645
|
+
hasFlag(argv, "--json") ? JSON.stringify(comparison.runs, null, 2) : renderAgentEvalCompare(comparison).trimEnd()
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// src/eval-template.ts
|
|
650
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
651
|
+
import { dirname, isAbsolute, relative, resolve, sep } from "path";
|
|
652
|
+
import { parse } from "yaml";
|
|
653
|
+
var RUNBOOK_TODO_MARKER = "<!-- cofactor:todo -->";
|
|
654
|
+
var RUNBOOK_SECTIONS = [
|
|
655
|
+
{
|
|
656
|
+
heading: "What this eval proves",
|
|
657
|
+
prompt: "Describe the agent's job in one paragraph and the capabilities it backs."
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
heading: "Anatomy of a case",
|
|
661
|
+
prompt: "List the files in $EVAL_INPUT, what the expected value holds, and what a good case teaches."
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
heading: "Finding real data",
|
|
665
|
+
prompt: "Say where in production the inputs live: exact cofactor commands, queries and ids, and how to pick varied cases."
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
heading: "Labeling",
|
|
669
|
+
prompt: "Say how the expected value is decided and what must corroborate it; never the agent's own output alone."
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
heading: "Sanitizing",
|
|
673
|
+
prompt: "Say what to strip or replace before upload, the check to run, and the neutral names to use (Acme, example.com)."
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
heading: "Synthesizing scenarios",
|
|
677
|
+
prompt: "Say how to make a case when no real one exists (mutate a frozen input) and how to mark it synthetic in --source."
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
heading: "Adding the case",
|
|
681
|
+
prompt: "Give the exact eval add-case command shape for this eval."
|
|
682
|
+
},
|
|
683
|
+
{ heading: "Known traps", prompt: "Record what has gone wrong before." }
|
|
684
|
+
];
|
|
685
|
+
var REQUIRED_SECTIONS = ["Finding real data", "Sanitizing"];
|
|
686
|
+
var EVAL_GITIGNORE = "*.tar.gz\n_staging/\n";
|
|
687
|
+
function initRunbook(name) {
|
|
688
|
+
return [
|
|
689
|
+
`# ${name} runbook`,
|
|
690
|
+
"",
|
|
691
|
+
...RUNBOOK_SECTIONS.flatMap(({ heading, prompt }) => [`## ${heading}`, "", RUNBOOK_TODO_MARKER, `<!-- ${prompt} -->`, ""])
|
|
692
|
+
].join("\n");
|
|
693
|
+
}
|
|
694
|
+
function initTemplate(opts) {
|
|
695
|
+
const capability = opts.capability ?? "CAP-REPLACE-ME";
|
|
696
|
+
const application = opts.application ?? "<application>";
|
|
697
|
+
return `# Agent eval: ${opts.name}
|
|
698
|
+
# Push it with:
|
|
699
|
+
# cofactor eval push evals/${opts.name}.eval.yaml --application ${application} --org <org>
|
|
700
|
+
# Add cases with:
|
|
701
|
+
# cofactor eval add-case ${opts.name} --key <key> --input <dir> --expected <file> --source "<provenance>" --org <org>
|
|
702
|
+
# Run it with:
|
|
703
|
+
# cofactor eval run ${opts.name} --org <org>
|
|
704
|
+
version: 1
|
|
705
|
+
name: ${opts.name}
|
|
706
|
+
description: Replace with one sentence on what this agent must get right.
|
|
707
|
+
|
|
708
|
+
# Capabilities in the application's product map that this eval proves (CAP- codes).
|
|
709
|
+
proves:
|
|
710
|
+
capabilities:
|
|
711
|
+
- ${capability}
|
|
712
|
+
|
|
713
|
+
# Runbook for whoever adds cases; path relative to this file. Stored with the eval.
|
|
714
|
+
runbook: ${opts.name}.runbook.md
|
|
715
|
+
|
|
716
|
+
# single-turn: \`run\` is executed once per attempt. multi-turn: \`run\` is the agent process,
|
|
717
|
+
# speaking JSONL on stdin/stdout, and a \`simulator\` model is required (cases then need a conversation).
|
|
718
|
+
mode: single-turn
|
|
719
|
+
|
|
720
|
+
# Shell command run at the repository root of the evaluated commit. It reads its case from
|
|
721
|
+
# $EVAL_INPUT and writes its answer to $EVAL_OUTPUT/answer.* (answer.json, answer.txt, ...).
|
|
722
|
+
# Model calls go to the injected ANTHROPIC_BASE_URL / OPENAI_BASE_URL; never use a real key.
|
|
723
|
+
run: ./scripts/run-agent.sh
|
|
724
|
+
|
|
725
|
+
input:
|
|
726
|
+
description: What the files in a case's input directory contain.
|
|
727
|
+
# Paths every case bundle must contain (files or directories); checked before upload.
|
|
728
|
+
require:
|
|
729
|
+
- input.json
|
|
730
|
+
|
|
731
|
+
agent:
|
|
732
|
+
models:
|
|
733
|
+
# alias: provider (anthropic | azure) and model id. Aliases are what --model takes.
|
|
734
|
+
sonnet:
|
|
735
|
+
provider: anthropic
|
|
736
|
+
model: claude-sonnet-4-6
|
|
737
|
+
default: sonnet
|
|
738
|
+
|
|
739
|
+
graders:
|
|
740
|
+
# A script grader runs after the agent exits with EVAL_INPUT, EVAL_OUTPUT and EVAL_EXPECTED
|
|
741
|
+
# (the case's expected value as a JSON file). Its last stdout line is
|
|
742
|
+
# {"pass": true, "score": 1, "reason": "why"}. Anything else is a grader error.
|
|
743
|
+
- id: answer-matches-expected
|
|
744
|
+
kind: script
|
|
745
|
+
command: ./scripts/grade-answer.sh
|
|
746
|
+
|
|
747
|
+
# An ai-judge scores a rubric against the answer and the case's expected value.
|
|
748
|
+
# The judge model is pinned. scale is binary or 1-5; a 1-5 scale requires passAt.
|
|
749
|
+
- id: answer-quality
|
|
750
|
+
kind: ai-judge
|
|
751
|
+
rubric: The answer states the same root cause as the expected value and cites evidence from the input.
|
|
752
|
+
scale: 1-5
|
|
753
|
+
passAt: 4
|
|
754
|
+
judge:
|
|
755
|
+
provider: anthropic
|
|
756
|
+
model: claude-opus-4-8
|
|
757
|
+
|
|
758
|
+
# Each case runs \`repeats\` times and passes when at least \`passAtLeast\` attempts pass.
|
|
759
|
+
repeats: 3
|
|
760
|
+
passAtLeast: 2
|
|
761
|
+
|
|
762
|
+
# The eval passes when this share of cases pass.
|
|
763
|
+
passBar: 0.5
|
|
764
|
+
|
|
765
|
+
# Time limit per attempt, in seconds (30 to 1700).
|
|
766
|
+
attemptSeconds: 600
|
|
767
|
+
`;
|
|
768
|
+
}
|
|
769
|
+
function formatIssues(error) {
|
|
770
|
+
return error.issues.map((issue) => {
|
|
771
|
+
const path = issue.path.map(String).join(".");
|
|
772
|
+
return `${path || "(template)"}: ${issue.message}`;
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
var TemplateError = class extends Error {
|
|
776
|
+
constructor(heading, lines) {
|
|
777
|
+
super(`${heading}
|
|
778
|
+
${lines.map((line) => ` ${line}`).join("\n")}`);
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
function sectionBodies(runbook) {
|
|
782
|
+
const sections = /* @__PURE__ */ new Map();
|
|
783
|
+
let current = null;
|
|
784
|
+
for (const line of runbook.split(/\r?\n/)) {
|
|
785
|
+
const heading = line.match(/^##\s+(.+?)\s*$/);
|
|
786
|
+
if (heading) {
|
|
787
|
+
current = heading[1];
|
|
788
|
+
sections.set(current, "");
|
|
789
|
+
} else if (current !== null) {
|
|
790
|
+
sections.set(current, `${sections.get(current)}${line}
|
|
791
|
+
`);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return sections;
|
|
795
|
+
}
|
|
796
|
+
function checkRunbook(runbook) {
|
|
797
|
+
const sections = sectionBodies(runbook);
|
|
798
|
+
const errors = [];
|
|
799
|
+
const warnings = [];
|
|
800
|
+
const todo = [...sections].filter(([, body]) => body.split("\n").some((line) => line.trim() === RUNBOOK_TODO_MARKER));
|
|
801
|
+
if (todo.length) {
|
|
802
|
+
errors.push(
|
|
803
|
+
`Runbook still has ${RUNBOOK_TODO_MARKER} in: ${todo.map(([heading]) => `"## ${heading}"`).join(", ")}. Write each section, then delete its marker line.`
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
for (const { heading } of RUNBOOK_SECTIONS) {
|
|
807
|
+
const body = sections.get(heading);
|
|
808
|
+
const empty = body === void 0 || body.replace(/<!--[\s\S]*?-->/g, "").trim() === "";
|
|
809
|
+
if (body === void 0) warnings.push(`Runbook has no "## ${heading}" section.`);
|
|
810
|
+
if (REQUIRED_SECTIONS.includes(heading) && empty) {
|
|
811
|
+
errors.push(`Runbook section "## ${heading}" is empty; write it before pushing.`);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
return { errors, warnings };
|
|
815
|
+
}
|
|
816
|
+
async function readPushPayload(file) {
|
|
817
|
+
let source;
|
|
818
|
+
try {
|
|
819
|
+
source = await readFile2(file, "utf8");
|
|
820
|
+
} catch {
|
|
821
|
+
throw new Error(`Cannot read ${file}.`);
|
|
822
|
+
}
|
|
823
|
+
let raw;
|
|
824
|
+
try {
|
|
825
|
+
raw = parse(source);
|
|
826
|
+
} catch (error) {
|
|
827
|
+
throw new Error(`${file} is not valid YAML: ${error instanceof Error ? error.message : String(error)}`);
|
|
828
|
+
}
|
|
829
|
+
const parsed = AgentEvalTemplateSchema.safeParse(raw);
|
|
830
|
+
if (!parsed.success) throw new TemplateError(`${file} is not a valid eval template:`, formatIssues(parsed.error));
|
|
831
|
+
const runbookPath = resolve(dirname(file), parsed.data.runbook);
|
|
832
|
+
const inside = relative(dirname(resolve(file)), runbookPath);
|
|
833
|
+
if (isAbsolute(parsed.data.runbook) || inside === ".." || inside.startsWith(`..${sep}`) || isAbsolute(inside))
|
|
834
|
+
throw new Error(`The template's runbook (${parsed.data.runbook}) must be a path inside the template's directory.`);
|
|
835
|
+
let runbook;
|
|
836
|
+
try {
|
|
837
|
+
runbook = await readFile2(runbookPath, "utf8");
|
|
838
|
+
} catch {
|
|
839
|
+
throw new Error(`Cannot read the runbook ${runbookPath} (the template's runbook: ${parsed.data.runbook}).`);
|
|
840
|
+
}
|
|
841
|
+
const { errors, warnings } = checkRunbook(runbook);
|
|
842
|
+
if (errors.length) throw new TemplateError(`${runbookPath} cannot be pushed:`, errors);
|
|
843
|
+
return { definition: parsed.data, runbook, warnings };
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// src/eval.ts
|
|
847
|
+
var USAGE = {
|
|
848
|
+
init: "init <name> [--application <app>] [--capability CAP-\u2026]",
|
|
849
|
+
push: "push [<file>] --application <app>",
|
|
850
|
+
show: "show <name>",
|
|
851
|
+
runbook: "runbook <name>",
|
|
852
|
+
list: "list",
|
|
853
|
+
cases: "cases <name>",
|
|
854
|
+
"rm-case": "rm-case <name> <key>",
|
|
855
|
+
"add-case": 'add-case <name> --key <k> --input <dir|file.tar.gz> --expected <file|-> --source "<provenance>" [--title <t>] [--conversation <file.json>]',
|
|
856
|
+
run: "run <name> [--model <alias>]... [--environment <env>] [--at <sha>] [--case <key>]... [--repeats N] [--parallel N] [--no-wait]",
|
|
857
|
+
get: "get <run-id> [--case <key>] [--transcript]",
|
|
858
|
+
runs: "runs <name> [--limit N]",
|
|
859
|
+
compare: "compare (<run-id>... | --batch <id>)"
|
|
860
|
+
};
|
|
861
|
+
var VALUE_FLAGS = [
|
|
862
|
+
...GLOBAL_VALUE_FLAGS,
|
|
863
|
+
"--application",
|
|
864
|
+
"--capability",
|
|
865
|
+
"--key",
|
|
866
|
+
"--input",
|
|
867
|
+
"--expected",
|
|
868
|
+
"--source",
|
|
869
|
+
"--title",
|
|
870
|
+
"--conversation",
|
|
871
|
+
"--model",
|
|
872
|
+
"--environment",
|
|
873
|
+
"--at",
|
|
874
|
+
"--case",
|
|
875
|
+
"--repeats",
|
|
876
|
+
"--parallel",
|
|
877
|
+
"--limit",
|
|
878
|
+
"--batch"
|
|
879
|
+
];
|
|
880
|
+
var POSITIONALS = {
|
|
881
|
+
show: 1,
|
|
882
|
+
runbook: 1,
|
|
883
|
+
cases: 1,
|
|
884
|
+
"rm-case": 2,
|
|
885
|
+
"add-case": 1,
|
|
886
|
+
run: 1,
|
|
887
|
+
get: 1,
|
|
888
|
+
runs: 1
|
|
889
|
+
};
|
|
890
|
+
var WRITES = ["push", "add-case", "rm-case", "run"];
|
|
891
|
+
var DEFAULT_TEMPLATE_DIR = "evals";
|
|
892
|
+
function printHelp(command) {
|
|
893
|
+
const usage = Object.hasOwn(USAGE, command ?? "") ? USAGE[command] : void 0;
|
|
894
|
+
console.log(
|
|
895
|
+
[
|
|
896
|
+
`Usage: cofactor eval ${usage ?? "<sub-command>"} [--json]`,
|
|
897
|
+
"",
|
|
898
|
+
...Object.values(USAGE).map((line) => ` cofactor eval ${line}`),
|
|
899
|
+
"",
|
|
900
|
+
"An eval runs an agent command against a set of cases at a commit, grades each attempt and reports",
|
|
901
|
+
"pass rate, cost and time per task. The template and runbook live in your repo under evals/.",
|
|
902
|
+
"",
|
|
903
|
+
"init: writes evals/<name>.eval.yaml (a commented template with a script and an ai-judge grader),",
|
|
904
|
+
" evals/<name>.runbook.md and evals/.gitignore (*.tar.gz, _staging/). Refuses to overwrite.",
|
|
905
|
+
"push: validates the template locally, reads the runbook it names (relative to the YAML) and stores both;",
|
|
906
|
+
" re-pushing is idempotent. Defaults to evals/*.eval.yaml when exactly one exists. Needs --application.",
|
|
907
|
+
' Warns on a missing runbook heading; errors when "## Finding real data" or "## Sanitizing" is empty, or when',
|
|
908
|
+
' any section still has the "<!-- cofactor:todo -->" line that init writes (delete it once the section is written).',
|
|
909
|
+
"show/list/cases/runbook: read the stored eval, its cases (key, sha256, size, source) and runbook.",
|
|
910
|
+
"add-case: --input is a directory (tarred deterministically: sorted entries, fixed mtime, uid/gid 0) or a .tar.gz.",
|
|
911
|
+
" The sha256 shown is that of the uncompressed tar, so the same files give the same sha256 on any runtime",
|
|
912
|
+
" and dedup is by tar content; gzip is transport only.",
|
|
913
|
+
" The template's input.require paths are checked first.",
|
|
914
|
+
" --expected is a JSON file, or - for stdin. --source says where the data came from and what corroborates the",
|
|
915
|
+
" label. Never include customer data in a case. --conversation <file.json> is required for a multi-turn eval.",
|
|
916
|
+
"rm-case: deletes a case that no run has used.",
|
|
917
|
+
"run: starts one run per --model (alias from the template; none means its default) and waits, printing one",
|
|
918
|
+
" section per model and then the comparison grid. --case, --repeats and a changed pass bar make a partial run",
|
|
919
|
+
" that never counts for coverage or proofs. Polls every 5 seconds for at most 8.5 hours; a timeout leaves the",
|
|
920
|
+
" runs readable with get; the batch and run ids are printed to stderr before waiting. --no-wait prints the",
|
|
921
|
+
" ids and exits 0. --json prints an array with one entry per run, in order: the run's result, or",
|
|
922
|
+
" {id, status, verdict, error} for a run with no result.",
|
|
923
|
+
"get: a run's result. --case <key> narrows to one case; --case <key> --transcript adds a multi-turn attempt's",
|
|
924
|
+
" conversation. runs: recent runs for an eval, newest first (--limit N).",
|
|
925
|
+
"compare: the grid for run ids or a --batch; --json prints the array of runs.",
|
|
926
|
+
"",
|
|
927
|
+
"Common options:",
|
|
928
|
+
" --org <name|id> Logged-in organization; required explicitly for push, add-case, rm-case and run",
|
|
929
|
+
" --env <env> API environment (prod, dev, local)",
|
|
930
|
+
" --api-url <url> API URL override",
|
|
931
|
+
" --token <key> API token override",
|
|
932
|
+
" --json Raw JSON on every sub-command except init",
|
|
933
|
+
" --help, -h Show help without authentication",
|
|
934
|
+
"",
|
|
935
|
+
"Requires the sandbox-v2 organization flag. API-key scopes:",
|
|
936
|
+
" run: sandbox:create + sandbox:exec, named explicitly (sandbox:* does not grant sandbox:exec).",
|
|
937
|
+
" push, add-case, rm-case: sandbox:edit. show, list, cases, runbook, get, runs, compare: sandbox:read.",
|
|
938
|
+
"",
|
|
939
|
+
"Exit codes (run):",
|
|
940
|
+
" 0 every run's verdict is pass",
|
|
941
|
+
` 3 at least one run's verdict is fail (the agent missed the pass bar; perf also exits 3 for "slower")`,
|
|
942
|
+
" 1 a failed or cancelled run, a polling timeout, or any error",
|
|
943
|
+
"Other sub-commands exit 0 on success, 1 on error."
|
|
944
|
+
].join("\n")
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
function required(argv, flag) {
|
|
948
|
+
const value = getScalarArgValue(argv, flag);
|
|
949
|
+
if (!value) throw new Error(`${flag} is required.`);
|
|
950
|
+
return value;
|
|
951
|
+
}
|
|
952
|
+
function expectedArg(argv) {
|
|
953
|
+
const i = argv.findIndex((arg) => arg === "--expected" || arg.startsWith("--expected="));
|
|
954
|
+
const value = i < 0 ? void 0 : argv[i].startsWith("--expected=") ? argv[i].slice("--expected=".length) : argv[i + 1];
|
|
955
|
+
if (!value || value.startsWith("--")) throw new Error("--expected is required: a JSON file, or - for stdin.");
|
|
956
|
+
return value;
|
|
957
|
+
}
|
|
958
|
+
async function readJson(path, label) {
|
|
959
|
+
let text;
|
|
960
|
+
try {
|
|
961
|
+
text = path === "-" ? await readStdin() : await readFile3(path, "utf8");
|
|
962
|
+
} catch (error) {
|
|
963
|
+
throw new Error(`Cannot read ${label}: ${error instanceof Error ? error.message : String(error)}`);
|
|
964
|
+
}
|
|
965
|
+
try {
|
|
966
|
+
return JSON.parse(text);
|
|
967
|
+
} catch (error) {
|
|
968
|
+
throw new Error(`${label} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
function target(ctx, command) {
|
|
972
|
+
const value = ctx.positionals[0];
|
|
973
|
+
if (!value) throw new Error(`Usage: cofactor eval ${USAGE[command]}`);
|
|
974
|
+
return value;
|
|
975
|
+
}
|
|
976
|
+
async function init(argv) {
|
|
977
|
+
const name = getPositionalArg(argv, VALUE_FLAGS);
|
|
978
|
+
if (!name) throw new Error(`Usage: cofactor eval ${USAGE.init}`);
|
|
979
|
+
if (!/^[a-z0-9-]{2,40}$/.test(name) || name === "runs" || name === "compare")
|
|
980
|
+
throw new Error('The eval name must be 2 to 40 lowercase letters, digits or dashes (and not "runs" or "compare").');
|
|
981
|
+
const capability = getScalarArgValue(argv, "--capability");
|
|
982
|
+
if (capability !== void 0 && !/^CAP-[A-Z0-9]+-\d{3,}$/.test(capability))
|
|
983
|
+
throw new Error("--capability must be a product-map code such as CAP-CMM-001.");
|
|
984
|
+
const files = [
|
|
985
|
+
{ path: join2(DEFAULT_TEMPLATE_DIR, `${name}.eval.yaml`), text: initTemplate({ name, application: getScalarArgValue(argv, "--application"), capability }) },
|
|
986
|
+
{ path: join2(DEFAULT_TEMPLATE_DIR, `${name}.runbook.md`), text: initRunbook(name) }
|
|
987
|
+
];
|
|
988
|
+
const gitignore = join2(DEFAULT_TEMPLATE_DIR, ".gitignore");
|
|
989
|
+
const exists = async (path) => readFile3(path).then(() => true, () => false);
|
|
990
|
+
for (const file of files) {
|
|
991
|
+
if (await exists(file.path)) throw new Error(`${file.path} already exists; not overwriting it.`);
|
|
992
|
+
}
|
|
993
|
+
await mkdir(DEFAULT_TEMPLATE_DIR, { recursive: true });
|
|
994
|
+
const written = files.map((file) => file.path);
|
|
995
|
+
for (const file of files) await writeFile(file.path, file.text, { flag: "wx" });
|
|
996
|
+
if (!await exists(gitignore)) {
|
|
997
|
+
await writeFile(gitignore, EVAL_GITIGNORE, { flag: "wx" });
|
|
998
|
+
written.push(gitignore);
|
|
999
|
+
}
|
|
1000
|
+
writeRawOutput(
|
|
1001
|
+
hasFlag(argv, "--json") ? JSON.stringify({ written }, null, 2) : `${written.map((path) => `Wrote ${path}`).join("\n")}
|
|
1002
|
+
Edit the run command and graders, write each runbook section and delete its <!-- cofactor:todo --> line, then: cofactor eval push ${files[0].path} --application <app> --org <org>`
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
async function defaultTemplateFile() {
|
|
1006
|
+
const found = (await readdir2(DEFAULT_TEMPLATE_DIR).catch(() => [])).filter((f) => f.endsWith(".eval.yaml"));
|
|
1007
|
+
if (found.length !== 1)
|
|
1008
|
+
throw new Error(
|
|
1009
|
+
found.length === 0 ? `No ${DEFAULT_TEMPLATE_DIR}/*.eval.yaml found; pass the template file.` : `Several templates in ${DEFAULT_TEMPLATE_DIR}/ (${found.join(", ")}); pass the one to push.`
|
|
1010
|
+
);
|
|
1011
|
+
return join2(DEFAULT_TEMPLATE_DIR, found[0]);
|
|
1012
|
+
}
|
|
1013
|
+
async function push({ argv, apiUrl, token, json }) {
|
|
1014
|
+
const file = getPositionalArg(argv, VALUE_FLAGS) ?? await defaultTemplateFile();
|
|
1015
|
+
const payload = await readPushPayload(file);
|
|
1016
|
+
for (const warning of payload.warnings) console.error(`Warning: ${warning}`);
|
|
1017
|
+
const applicationId = await resolveApplicationId(argv, apiUrl, token);
|
|
1018
|
+
const pushed = await request(apiUrl, token, "PUT", EVALS, {
|
|
1019
|
+
applicationId,
|
|
1020
|
+
definition: payload.definition,
|
|
1021
|
+
runbook: payload.runbook
|
|
1022
|
+
});
|
|
1023
|
+
const detail = await getEval(apiUrl, token, pushed.name);
|
|
1024
|
+
if (json) {
|
|
1025
|
+
writeRawOutput(JSON.stringify({ ...detail, warnings: payload.warnings }, null, 2));
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
writeRawOutput(
|
|
1029
|
+
[
|
|
1030
|
+
`Pushed ${detail.name} (${detail.id})`,
|
|
1031
|
+
`Cases: ${detail.cases.length}`,
|
|
1032
|
+
`Capabilities: ${detail.capabilities.map((c) => c.code).join(", ") || "none"}`
|
|
1033
|
+
].join("\n")
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
function describeEval(detail) {
|
|
1037
|
+
const d = detail.definition;
|
|
1038
|
+
return [
|
|
1039
|
+
`${detail.name} ${detail.id}`,
|
|
1040
|
+
d.description,
|
|
1041
|
+
`Application: ${detail.applicationId}`,
|
|
1042
|
+
`Mode: ${d.mode}`,
|
|
1043
|
+
`Run: ${d.run.split("\n")[0]}${d.run.includes("\n") ? " \u2026" : ""}`,
|
|
1044
|
+
`Models: ${Object.entries(d.agent.models).map(([alias, m]) => `${alias}${alias === d.agent.default ? "*" : ""} (${m.model})`).join(", ")}`,
|
|
1045
|
+
`Graders: ${d.graders.map((g) => `${g.id} [${g.kind}]`).join(", ")}`,
|
|
1046
|
+
`Repeats: ${d.repeats}, pass at least ${d.passAtLeast}, bar ${Math.round(d.passBar * 100)}%, ${d.attemptSeconds}s per attempt`,
|
|
1047
|
+
`Capabilities: ${detail.capabilities.map((c) => `${c.code} ${c.title}`).join("; ") || "none"}`,
|
|
1048
|
+
`Cases: ${detail.cases.length}`
|
|
1049
|
+
].join("\n");
|
|
1050
|
+
}
|
|
1051
|
+
async function listEvals({ apiUrl, token, json }) {
|
|
1052
|
+
const evals = await requestAll(apiUrl, token, EVALS);
|
|
1053
|
+
writeRawOutput(
|
|
1054
|
+
json ? JSON.stringify(evals, null, 2) : evals.map((e) => `${e.name} ${e.id} ${e.caseCount} cases ${e.latestVerdict ?? "-"} ${e.description}`).join("\n") || "No evals found."
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
async function showEval(ctx) {
|
|
1058
|
+
const detail = await getEval(ctx.apiUrl, ctx.token, target(ctx, "show"));
|
|
1059
|
+
writeRawOutput(ctx.json ? JSON.stringify(detail, null, 2) : describeEval(detail));
|
|
1060
|
+
}
|
|
1061
|
+
async function listCases(ctx) {
|
|
1062
|
+
const { cases } = await getEval(ctx.apiUrl, ctx.token, target(ctx, "cases"));
|
|
1063
|
+
writeRawOutput(
|
|
1064
|
+
ctx.json ? JSON.stringify(cases, null, 2) : cases.map((c) => `${c.key} ${c.bundleSha256.slice(0, 12)} ${c.bundleBytes} bytes ${c.title} [${c.source}]`).join("\n") || "No cases."
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
async function showRunbook(ctx) {
|
|
1068
|
+
const name = target(ctx, "runbook");
|
|
1069
|
+
const text = await requestText(ctx.apiUrl, ctx.token, `${EVALS}/${encodeURIComponent(name)}/runbook`);
|
|
1070
|
+
writeRawOutput(ctx.json ? JSON.stringify({ name, runbook: text }, null, 2) : text.trimEnd());
|
|
1071
|
+
}
|
|
1072
|
+
async function removeCase(ctx) {
|
|
1073
|
+
const name = target(ctx, "rm-case");
|
|
1074
|
+
const key = ctx.positionals[1];
|
|
1075
|
+
if (!key) throw new Error(`Usage: cofactor eval ${USAGE["rm-case"]}`);
|
|
1076
|
+
const evaluation = await getEval(ctx.apiUrl, ctx.token, name);
|
|
1077
|
+
await request(ctx.apiUrl, ctx.token, "DELETE", `${EVALS}/${encodeURIComponent(evaluation.id)}/cases/${encodeURIComponent(key)}`);
|
|
1078
|
+
writeRawOutput(ctx.json ? JSON.stringify({ eval: name, key, deleted: true }) : `Removed case ${key} from ${name}`);
|
|
1079
|
+
}
|
|
1080
|
+
async function addCase(ctx) {
|
|
1081
|
+
const { argv, apiUrl, token, json } = ctx;
|
|
1082
|
+
const name = target(ctx, "add-case");
|
|
1083
|
+
const key = required(argv, "--key");
|
|
1084
|
+
const input = required(argv, "--input");
|
|
1085
|
+
const expectedSource = expectedArg(argv);
|
|
1086
|
+
const source = required(argv, "--source");
|
|
1087
|
+
const conversationFile = getScalarArgValue(argv, "--conversation");
|
|
1088
|
+
if (!/^[a-z0-9][a-z0-9-]{0,99}$/.test(key))
|
|
1089
|
+
throw new Error("--key must be lowercase letters, digits and dashes, starting with a letter or digit (at most 100 characters).");
|
|
1090
|
+
const expected = await readJson(expectedSource, expectedSource === "-" ? "the expected value on stdin" : `--expected ${expectedSource}`);
|
|
1091
|
+
let conversation;
|
|
1092
|
+
if (conversationFile) {
|
|
1093
|
+
const parsed = EvalConversationSchema.safeParse(await readJson(conversationFile, `--conversation ${conversationFile}`));
|
|
1094
|
+
if (!parsed.success)
|
|
1095
|
+
throw new TemplateError(`${conversationFile} is not a valid conversation:`, formatIssues(parsed.error));
|
|
1096
|
+
conversation = parsed.data;
|
|
1097
|
+
}
|
|
1098
|
+
const evaluation = await getEval(apiUrl, token, name);
|
|
1099
|
+
const multiTurn = evaluation.definition.mode === "multi-turn";
|
|
1100
|
+
if (multiTurn && !conversation) throw new Error(`${name} is multi-turn: pass --conversation <file.json>.`);
|
|
1101
|
+
if (!multiTurn && conversation) throw new Error(`${name} is single-turn: --conversation is only for multi-turn evals.`);
|
|
1102
|
+
const bundle = await loadBundle(input);
|
|
1103
|
+
const missing = missingRequiredPaths(bundle.entries, evaluation.definition.input.require);
|
|
1104
|
+
if (missing.length)
|
|
1105
|
+
throw new Error(`The bundle is missing required input path(s): ${missing.join(", ")} (the template's input.require).`);
|
|
1106
|
+
const evalPath = `${EVALS}/${encodeURIComponent(evaluation.id)}`;
|
|
1107
|
+
const upload = await request(apiUrl, token, "POST", `${evalPath}/uploads`, {
|
|
1108
|
+
sha256: bundle.sha256,
|
|
1109
|
+
gzipSha256: bundle.gzipSha256,
|
|
1110
|
+
bytes: bundle.bytes
|
|
1111
|
+
});
|
|
1112
|
+
const deduplicated = upload.status === "exists";
|
|
1113
|
+
if (upload.status === "upload") {
|
|
1114
|
+
const response = await fetch(upload.url, { method: "PUT", headers: upload.headers, body: new Uint8Array(bundle.archive) });
|
|
1115
|
+
if (!response.ok)
|
|
1116
|
+
throw new Error(`Uploading the bundle failed (${response.status}): ${(await response.text()).slice(0, 300)}`);
|
|
1117
|
+
}
|
|
1118
|
+
await request(apiUrl, token, "POST", `${evalPath}/cases`, {
|
|
1119
|
+
key,
|
|
1120
|
+
title: getScalarArgValue(argv, "--title") ?? key,
|
|
1121
|
+
sha256: bundle.sha256,
|
|
1122
|
+
expected,
|
|
1123
|
+
source,
|
|
1124
|
+
...conversation ? { conversation } : {}
|
|
1125
|
+
});
|
|
1126
|
+
const result = { eval: name, key, sha256: bundle.sha256, bytes: bundle.bytes, deduplicated };
|
|
1127
|
+
writeRawOutput(
|
|
1128
|
+
json ? JSON.stringify(result, null, 2) : `Added case ${key} to ${name}
|
|
1129
|
+
sha256 ${bundle.sha256} (${bundle.bytes} bytes local gzip), ${deduplicated ? "deduplicated: the bundle already existed" : "uploaded"}`
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
var HANDLERS = {
|
|
1133
|
+
push,
|
|
1134
|
+
show: showEval,
|
|
1135
|
+
runbook: showRunbook,
|
|
1136
|
+
list: listEvals,
|
|
1137
|
+
cases: listCases,
|
|
1138
|
+
"rm-case": removeCase,
|
|
1139
|
+
"add-case": addCase,
|
|
1140
|
+
run: (ctx) => runEval(ctx.argv, target(ctx, "run"), ctx.apiUrl, ctx.token),
|
|
1141
|
+
get: (ctx) => getRun(ctx.argv, target(ctx, "get"), ctx.apiUrl, ctx.token),
|
|
1142
|
+
runs: (ctx) => listRuns(ctx.argv, target(ctx, "runs"), ctx.apiUrl, ctx.token),
|
|
1143
|
+
compare: (ctx) => compareRuns(ctx.argv, ctx.positionals, ctx.apiUrl, ctx.token)
|
|
1144
|
+
};
|
|
1145
|
+
async function runEvalCommand(argv) {
|
|
1146
|
+
const [command, ...rest] = argv;
|
|
1147
|
+
if (!command || command === "help" || hasFlag(argv, "--help", "-h")) {
|
|
1148
|
+
printHelp(command === "help" ? rest[0] : command);
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
let code = 0;
|
|
1152
|
+
try {
|
|
1153
|
+
if (!Object.hasOwn(USAGE, command)) throw new Error(`Unknown eval command: ${command}`);
|
|
1154
|
+
const cmd = command;
|
|
1155
|
+
if (WRITES.includes(cmd)) required(rest, "--org");
|
|
1156
|
+
for (const flag of VALUE_FLAGS.filter((flag2) => flag2 !== "--expected")) {
|
|
1157
|
+
if (rest.includes(flag) && getScalarArgValue(rest, flag) === void 0) throw new Error(`${flag} requires a value.`);
|
|
1158
|
+
}
|
|
1159
|
+
const positionals = getPositionalArgs(rest, VALUE_FLAGS);
|
|
1160
|
+
if (positionals.length < (POSITIONALS[cmd] ?? 0)) throw new Error(`Usage: cofactor eval ${USAGE[cmd]}`);
|
|
1161
|
+
if (cmd === "init") {
|
|
1162
|
+
await init(rest);
|
|
1163
|
+
} else {
|
|
1164
|
+
const { apiUrl, token } = await resolveConfig(rest);
|
|
1165
|
+
const ctx = { argv: rest, positionals, apiUrl, token, json: hasFlag(rest, "--json") };
|
|
1166
|
+
code = await HANDLERS[cmd](ctx) ?? 0;
|
|
1167
|
+
}
|
|
1168
|
+
} catch (error) {
|
|
1169
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
1170
|
+
code = 1;
|
|
1171
|
+
}
|
|
1172
|
+
if (code !== 0) process.exit(code);
|
|
1173
|
+
}
|
|
1174
|
+
export {
|
|
1175
|
+
runEvalCommand
|
|
1176
|
+
};
|
|
1177
|
+
//# sourceMappingURL=eval-SO5TPD37.js.map
|