promptfoo 0.120.25 → 0.120.26
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 +35 -22
- package/dist/src/{ListApp-CYTAaS6A.js → ListApp-D3DG0F8h.js} +1 -1
- package/dist/src/{accounts-CwqY22lU.cjs → accounts-BzEY8H3v.cjs} +4 -4
- package/dist/src/{accounts-Bzx8GnL6.js → accounts-DHHiXsy6.js} +5 -5
- package/dist/src/{accounts-BSwMxhY3.js → accounts-R3gfCR_g.js} +4 -4
- package/dist/src/{agentic-utils-5K7LzooV.js → agentic-utils-D6_gzOUF.js} +2 -2
- package/dist/src/{agents-B8RWrA68.cjs → agents-C7BiinFI.cjs} +6 -6
- package/dist/src/{agents-zHyWnnQ2.js → agents-Cao4i7AX.js} +7 -7
- package/dist/src/{agents-KZxXwlyn.cjs → agents-CwM7re15.cjs} +2 -2
- package/dist/src/{agents-DleC7rb4.js → agents-DETIQHqF.js} +6 -6
- package/dist/src/{agents-CkA9l77-.js → agents-DYIT-hQy.js} +5 -5
- package/dist/src/{agents-B20gl7hH.js → agents-GiUianme.js} +2 -2
- package/dist/src/{aimlapi-DgbFGPTX.js → aimlapi-CMJpKK-B.js} +9 -9
- package/dist/src/{aimlapi-DILJovhN.js → aimlapi-DMF6v_vb.js} +14 -14
- package/dist/src/{aimlapi-DIdWBHtH.cjs → aimlapi-DoGLcQW_.cjs} +9 -9
- package/dist/src/app/assets/index-BOgkICuY.css +1 -0
- package/dist/src/app/assets/index-CSgqn_Vd.js +428 -0
- package/dist/src/app/assets/{vendor-charts-DnwydkkY.js → vendor-charts-CYyo8R8v.js} +1 -1
- package/dist/src/app/assets/{vendor-markdown-DjKAU15k.js → vendor-markdown-DSmzq4Jh.js} +1 -1
- package/dist/src/app/assets/vendor-react-AtKqiNEf.js +4 -0
- package/dist/src/app/assets/{vendor-syntax-Dc86c4Bu.js → vendor-syntax-D06x6TQF.js} +1 -1
- package/dist/src/app/index.html +6 -6
- package/dist/src/app/tsconfig.app.tsbuildinfo +1 -1
- package/dist/src/{audio-BoR9KZV8.cjs → audio-BWjyvHn9.cjs} +3 -3
- package/dist/src/{audio-nUSOpXY7.js → audio-BrJBFN2b.js} +3 -3
- package/dist/src/{audio-BDrAkpVc.js → audio-DUH4q0Xq.js} +4 -4
- package/dist/src/{base-DumUXbOH.js → base-BaXmtXYp.js} +2 -2
- package/dist/src/{base-CwCV0t8r.js → base-Dtp8b4_N.js} +2 -2
- package/dist/src/{base-D8VQYhNQ.cjs → base-f71xxWai.cjs} +2 -2
- package/dist/src/{blobs-_kNRY6vU.cjs → blobs-673H0jCl.cjs} +2 -2
- package/dist/src/{blobs-Dnt2rxpc.js → blobs-C9J2mVgo.js} +2 -2
- package/dist/src/{blobs-DlB5fwyF.js → blobs-kt8v3UyH.js} +2 -2
- package/dist/src/{cache-S9MAoF9H.cjs → cache-7xULbvt3.cjs} +34 -7
- package/dist/src/{cache-C4A4H4Xb.js → cache-BLLayYEN.js} +23 -8
- package/dist/src/{cache-B2oIvf92.js → cache-BUPcq0Ad.js} +2 -2
- package/dist/src/cache-CVfRb-HD.cjs +6 -0
- package/dist/src/{cache-DofebEnH.js → cache-O4EuX2JV.js} +3 -3
- package/dist/src/{cache-Bg1Wpvzq.js → cache-mIszOnuz.js} +4 -21
- package/dist/src/{chat-DTCPbuld.cjs → chat-C4zqjObh.cjs} +6 -6
- package/dist/src/{chat-noXtEAh5.js → chat-C8Ei4f87.js} +6 -6
- package/dist/src/{chat-BiImdLJf.js → chat-CgyGj2hC.js} +8 -8
- package/dist/src/{chat-DQ6Z5Cbn.js → chat-Cpz3O-Xl.js} +13 -13
- package/dist/src/{chat-D_cro0wd.js → chat-DIywASPG.js} +10 -10
- package/dist/src/{chat-BR2x1dG_.cjs → chat-Fl6TZJRS.cjs} +8 -8
- package/dist/src/{chatkit-TnC1YoVi.js → chatkit-DEls11hE.js} +3 -3
- package/dist/src/{claude-agent-sdk-CBpAZV5h.js → claude-agent-sdk-6-xTaLwM.js} +45 -16
- package/dist/src/{claude-agent-sdk-uHLgAUZj.cjs → claude-agent-sdk-BzxF6NIJ.cjs} +45 -16
- package/dist/src/{claude-agent-sdk-qxE5mGVi.js → claude-agent-sdk-CmkTnKGH.js} +48 -19
- package/dist/src/{claude-agent-sdk-DyPvrj-1.js → claude-agent-sdk-rXCBLK_o.js} +38 -9
- package/dist/src/{cloud-lRosK6Nk.js → cloud-BMbRVJFw.js} +2 -2
- package/dist/src/{cloud-DHWE-9nj.js → cloud-CZ4hytdm.js} +1 -1
- package/dist/src/{cloudflare-ai-D0OtFY6g.cjs → cloudflare-ai-BAQ0u_dg.cjs} +9 -9
- package/dist/src/{cloudflare-ai-BprwSD6d.js → cloudflare-ai-CUg4BTcj.js} +9 -9
- package/dist/src/{cloudflare-ai-CB7OWRAP.js → cloudflare-ai-CobxMTR4.js} +14 -14
- package/dist/src/{cloudflare-gateway-C29gWj5O.cjs → cloudflare-gateway-C0sgfr_z.cjs} +10 -10
- package/dist/src/{cloudflare-gateway-BaVwoBjE.js → cloudflare-gateway-D2_yi-Fh.js} +15 -15
- package/dist/src/{cloudflare-gateway-BjoxwqUw.js → cloudflare-gateway-_itGuXry.js} +10 -10
- package/dist/src/{codex-sdk-DNkCbb7c.js → codex-sdk-dSnGdgIp.js} +3 -3
- package/dist/src/{cometapi-BGxGZ7VA.js → cometapi-B01btbfb.js} +15 -15
- package/dist/src/{cometapi-xiTQZp8R.js → cometapi-ChAaRjg5.js} +10 -10
- package/dist/src/{cometapi-DalITP94.cjs → cometapi-JbvOJSCO.cjs} +10 -10
- package/dist/src/{completion-BTbouQDz.js → completion-BBJ6zmG3.js} +5 -5
- package/dist/src/{completion-HYrX5gEL.js → completion-D9_MDlnd.js} +4 -4
- package/dist/src/{completion-DV0Ys99K.cjs → completion-DDyL3Cb2.cjs} +4 -4
- package/dist/src/{docker-Dx_FN1ag.js → docker-BuButc4D.js} +14 -14
- package/dist/src/{docker-zUAnbYTM.cjs → docker-Ckw-j7Rr.cjs} +9 -9
- package/dist/src/{docker-BAYnlbAW.js → docker-JAAubMw3.js} +9 -9
- package/dist/src/errors-DnGCbnx8.js +36 -0
- package/dist/src/{esm-79BpbQzg.js → esm-CYhseqj4.js} +1 -1
- package/dist/src/eval-CKHWqG9f.js +16 -0
- package/dist/src/{eval-Cxb9alW8.js → eval-CYrbG57o.js} +45 -13
- package/dist/src/{evalResult-B8LbgWMA.js → evalResult-6JaUIStC.js} +6 -6
- package/dist/src/{evalResult-CJNSpa3q.js → evalResult-COsVttMA.js} +6 -6
- package/dist/src/evalResult-CxTP-LMm.cjs +11 -0
- package/dist/src/evalResult-CzLURDcP.js +13 -0
- package/dist/src/{evalResult-hI1mhi_g.cjs → evalResult-DlRfu_Rq.cjs} +6 -6
- package/dist/src/evalResult-DyttNQ_G.js +11 -0
- package/dist/src/evaluator-0PvfeBYh.js +38 -0
- package/dist/src/{evaluator-1Tkdu8MK.js → evaluator-3EJCMTs0.js} +74 -48
- package/dist/src/{extractor-BPBlLyOn.cjs → extractor-DbhlYEeo.cjs} +22 -5
- package/dist/src/{extractor-CuUWqwrE.js → extractor-Hs7la_19.js} +22 -5
- package/dist/src/{extractor-BFoThUOa.js → extractor-LSYjrhK0.js} +22 -5
- package/dist/src/{fetch-zJuFPxN1.js → fetch-18MuNu9i.js} +4 -4
- package/dist/src/{fetch-BdiCecAv.js → fetch-Bi0o-fdp.js} +1 -1
- package/dist/src/fetch-CMptBDVg.cjs +4 -0
- package/dist/src/{fetch-BQgz6x7S.js → fetch-DAZkv3gV.js} +2 -2
- package/dist/src/{fetch-BuP-gm0E.js → fetch-SRsE6Ctl.js} +4 -4
- package/dist/src/{fetch-Dy2YML9G.cjs → fetch-ZMn_oemb.cjs} +4 -4
- package/dist/src/{fileExtensions-CzcPkiKZ.js → fileExtensions-BpuMmaFL.js} +1 -1
- package/dist/src/{formatDuration-CGa0ZDQ8.js → formatDuration-Doo0xq-z.js} +1 -1
- package/dist/src/{genaiTracer-foKtbVa2.js → genaiTracer-CqNnnXrE.js} +1 -1
- package/dist/src/graders-BCytzXrb.js +34 -0
- package/dist/src/{graders-DMAzEgEF.js → graders-BaMCwIKp.js} +211 -91
- package/dist/src/graders-CGZQShfJ.cjs +33 -0
- package/dist/src/{graders-NBsuYeMx.cjs → graders-DzUUnUjC.cjs} +206 -86
- package/dist/src/{graders-CtvuaC8D.js → graders-QsALpIdy.js} +206 -86
- package/dist/src/graders-spkuVC-E.js +36 -0
- package/dist/src/{image-Bf8RiJ2g.js → image-BXt_7u0v.js} +3 -3
- package/dist/src/{image-LhLO5KCW.js → image-BiEVdpdP.js} +4 -4
- package/dist/src/{image-CaVWfZub.cjs → image-C3BjJUAU.cjs} +4 -4
- package/dist/src/{image-BnF25Osq.cjs → image-D10zEe1f.cjs} +3 -3
- package/dist/src/{image-DIn_AW1Z.js → image-DB4sHxdJ.js} +4 -4
- package/dist/src/{image-DFwp1aAV.js → image-mhAGP07h.js} +6 -6
- package/dist/src/index.cjs +152 -69
- package/dist/src/index.d.cts +20 -3
- package/dist/src/index.d.ts +20 -3
- package/dist/src/index.js +152 -69
- package/dist/src/{interactiveCheck-Dmm9vjpx.js → interactiveCheck-DU-MAhp5.js} +1 -1
- package/dist/src/{knowledgeBase-Bf57PI7g.cjs → knowledgeBase-Bnb00xKs.cjs} +6 -6
- package/dist/src/{knowledgeBase-DZw2L9kf.js → knowledgeBase-CMvMlLZR.js} +8 -8
- package/dist/src/{knowledgeBase-CfOvyF2s.js → knowledgeBase-XJQ0Qyez.js} +6 -6
- package/dist/src/{litellm-Ds8YmO1b.js → litellm-BECdjOTx.js} +3 -3
- package/dist/src/{litellm-ByNRN2Vh.cjs → litellm-BrnZhMcL.cjs} +11 -11
- package/dist/src/{litellm-fU-xV6T4.js → litellm-CHrRmPAe.js} +16 -16
- package/dist/src/{litellm-DexU8FFp.js → litellm-CrLJrPIm.js} +11 -11
- package/dist/src/{luma-ray-y26EHFO8.js → luma-ray-BOeX-h0M.js} +8 -8
- package/dist/src/{luma-ray-BIe5ctI4.cjs → luma-ray-C0RkI3lt.cjs} +8 -8
- package/dist/src/{luma-ray-vvE-O9EA.js → luma-ray-DgKLS0BF.js} +10 -10
- package/dist/src/main.js +1011 -404
- package/dist/src/{messages-Bg2ekFPc.js → messages-CDZYGNlS.js} +7 -7
- package/dist/src/{messages-DpoCLD_8.js → messages-D61tPFQo.js} +9 -9
- package/dist/src/{messages-16jb9LP7.cjs → messages-DXV3Qh8_.cjs} +7 -7
- package/dist/src/{meteor-DLCdreag.js → meteor-P2rUE-Uz.js} +1 -1
- package/dist/src/{nova-reel-BM0snD2m.js → nova-reel-Bk5npr2q.js} +10 -10
- package/dist/src/{nova-reel-CvXCVn3f.cjs → nova-reel-D9FXq3Mt.cjs} +8 -8
- package/dist/src/{nova-reel-CJLpBKSI.js → nova-reel-DtCjbD5O.js} +8 -8
- package/dist/src/{nova-sonic-CklvraBd.js → nova-sonic-BXRfQyF-.js} +7 -7
- package/dist/src/{nova-sonic-DEx_FdFW.cjs → nova-sonic-BoRSY_U6.cjs} +5 -5
- package/dist/src/{nova-sonic-Cjh_qopr.js → nova-sonic-CgaWLDM1.js} +5 -5
- package/dist/src/{openai-jNIREoAW.js → openai-CT5fwbve.js} +1 -1
- package/dist/src/{openclaw-CnAwBfPO.js → openclaw-B6XY2kUf.js} +14 -14
- package/dist/src/{openclaw-DBSSY4SP.js → openclaw-CpPrXwf6.js} +9 -9
- package/dist/src/{openclaw-CbHtt3B8.cjs → openclaw-DDSfq5fp.cjs} +9 -9
- package/dist/src/{opencode-sdk-CqxTHrk0.js → opencode-sdk-CImWVqy9.js} +6 -6
- package/dist/src/{opencode-sdk-xUrRk5Jx.js → opencode-sdk-CuCztr4P.js} +4 -4
- package/dist/src/{opencode-sdk-DdmtRB6D.cjs → opencode-sdk-mqF-Oj3f.cjs} +4 -4
- package/dist/src/{otlpReceiver-th2zHwjZ.cjs → otlpReceiver-B6Xo4KZM.cjs} +4 -4
- package/dist/src/{otlpReceiver-uytrRKdo.js → otlpReceiver-BO0rbDzh.js} +4 -4
- package/dist/src/{otlpReceiver-C-1cdVDr.js → otlpReceiver-Dg817agV.js} +5 -5
- package/dist/src/{providerRegistry-DAZoFmGv.js → providerRegistry-Xf0qdqGQ.js} +1 -1
- package/dist/src/providers-BMZZmPBJ.cjs +32 -0
- package/dist/src/{providers-CpUrPa8s.cjs → providers-BNKVY53V.cjs} +94 -64
- package/dist/src/{providers-D3lk_R7Q.js → providers-BiNq_Iyc.js} +94 -64
- package/dist/src/{providers-BOvl9qv5.js → providers-BlEhY5mi.js} +110 -79
- package/dist/src/providers-CQQrNaJk.js +32 -0
- package/dist/src/providers-Ck8HyrC-.js +34 -0
- package/dist/src/{pythonUtils-CUPlf1H0.js → pythonUtils-r1uBuA0n.js} +2 -2
- package/dist/src/{quiverai-BNGJOQym.cjs → quiverai-BNfIwKCO.cjs} +8 -8
- package/dist/src/{quiverai-Y7Is4wah.js → quiverai-BQigKdIH.js} +13 -13
- package/dist/src/{quiverai-DthRqZs9.js → quiverai-Bfy2WnE2.js} +8 -8
- package/dist/src/{render-kFatbqG6.js → render-CAZvKKkB.js} +3 -3
- package/dist/src/{responses-BtU_i-R0.js → responses-DLLjADw5.js} +5 -5
- package/dist/src/{responses-B85halqh.js → responses-TsdODUpm.js} +8 -8
- package/dist/src/{responses-CXJACENh.cjs → responses-zOtKtnY_.cjs} +5 -5
- package/dist/src/{rubyUtils-DYKoa7Ze.js → rubyUtils-BtjlqyXt.js} +2 -2
- package/dist/src/{rubyUtils-BA4D4gMX.js → rubyUtils-D7--T12C.js} +2 -2
- package/dist/src/{sagemaker-hRS_Jj3-.js → sagemaker-BcgLu0U4.js} +17 -17
- package/dist/src/{sagemaker-DI6IOTjT.js → sagemaker-CLdUAv5z.js} +11 -11
- package/dist/src/{sagemaker-BUagsrJ-.cjs → sagemaker-DwNnEVYt.cjs} +11 -11
- package/dist/src/{scanner-C6QXA9Pn.js → scanner-Dyw21Wg_.js} +10 -10
- package/dist/src/server/index.js +1376 -486
- package/dist/src/server-BUbS0Qfh.js +6 -0
- package/dist/src/{server-BPC37wlX.js → server-CbMTRQkg.js} +3 -3
- package/dist/src/{server-C_O6-VE4.cjs → server-CgUQ25qW.cjs} +3 -3
- package/dist/src/{server-HQtz_gUy.js → server-DWmZLfCy.js} +4 -4
- package/dist/src/server-XpGXFHkS.cjs +6 -0
- package/dist/src/server-gfOx5Zrk.js +8 -0
- package/dist/src/{signal-D4VhSJOY.js → signal-Bl32q42d.js} +2 -2
- package/dist/src/{slack-DTUSadx4.js → slack-BfdBx2tO.js} +1 -1
- package/dist/src/{store-BTdkdmK9.js → store-2ocbYY9D.js} +2 -2
- package/dist/src/store-5u2yriTV.js +7 -0
- package/dist/src/{store-F-KTiqfb.cjs → store-BqwfFEyF.cjs} +2 -2
- package/dist/src/{store-rueRdzcm.js → store-D4gdn9ih.js} +2 -2
- package/dist/src/store-D_lq_8oQ.js +6 -0
- package/dist/src/store-m5KT6Ly7.cjs +6 -0
- package/dist/src/{tables-w-r-abWq.cjs → tables-B9E1kRp-.cjs} +2 -2
- package/dist/src/{tables-ChjAuVAR.js → tables-C7TT2XVn.js} +2 -2
- package/dist/src/{tables-dNaVPyDr.js → tables-D-NSwNIb.js} +2 -2
- package/dist/src/telemetry-5RHFoCJh.js +6 -0
- package/dist/src/{telemetry-wXyCQ1Nz.js → telemetry-BXyVqyAg.js} +3 -3
- package/dist/src/{telemetry-Y1esJM0v.cjs → telemetry-D0_yFdtU.cjs} +3 -3
- package/dist/src/{telemetry-Dai7PdlR.js → telemetry-DZ_7PaVq.js} +3 -3
- package/dist/src/telemetry-Do8wMnA-.js +8 -0
- package/dist/src/telemetry-LojxPoFq.cjs +6 -0
- package/dist/src/{text-C1cXk9ij.js → text-DgMr_tiM.js} +1 -1
- package/dist/src/{tokenUsageUtils-D2ko2eFQ.js → tokenUsageUtils-DmZSD2eU.js} +1 -1
- package/dist/src/{transcription-i6EfSMie.js → transcription-CYuY5sFO.js} +6 -6
- package/dist/src/{transcription-CcYTL4wi.js → transcription-Ch7S-LWw.js} +10 -10
- package/dist/src/{transcription-C-K2hDJX.cjs → transcription-FNIz3YOe.cjs} +6 -6
- package/dist/src/{transform-gpzlPrwG.js → transform-CoP2bJ7P.js} +43 -4
- package/dist/src/{transform-B86_0IIb.js → transform-CqTFr7KR.js} +4 -4
- package/dist/src/{transform-DosdxVdh.js → transform-DMaxQwDx.js} +43 -4
- package/dist/src/{transform-DwsntMEQ.js → transform-GybT0X0u.js} +3 -3
- package/dist/src/{transform-CCSNqTI-.cjs → transform-ivxEY4f7.cjs} +54 -3
- package/dist/src/{transformersAvailability-DM54tWzt.js → transformersAvailability-DwmezkVe.js} +1 -1
- package/dist/src/{types-Brw5kggg.cjs → types-BIfttHrT.cjs} +13 -1
- package/dist/src/{types-CslP2TyG.js → types-DMVjYLpx.js} +13 -1
- package/dist/src/{types-CMDs7Kzu.js → types-t52w-XsS.js} +16 -2
- package/dist/src/{util-BECSJ0T_.js → util-BSh4a_Q8.js} +6 -6
- package/dist/src/{util-D8z5QOy8.cjs → util-C08Kns6-.cjs} +3 -3
- package/dist/src/{util-CttPoePn.js → util-CUEt0Vum.js} +3 -3
- package/dist/src/{util-CfPdWQJX.js → util-Cl0zfT3V.js} +3 -3
- package/dist/src/{util-Dab5xp2E.cjs → util-DUYOvxAy.cjs} +3 -3
- package/dist/src/{util-CY_XguB9.js → util-DiCePfDu.js} +3 -3
- package/dist/src/{util-CBAXTxfy.cjs → util-DkFTvieG.cjs} +3 -3
- package/dist/src/{util-Dft1RxsL.js → util-mJ58qbbw.js} +3 -3
- package/dist/src/{util-Ba18wQ8e.js → util-vjscpUzy.js} +3 -3
- package/dist/src/{utils-BTeLHwiH.js → utils-CVzb4YiI.js} +2 -2
- package/dist/src/{utils-Db5KwiXp.cjs → utils-DFaZa6Rf.cjs} +2 -2
- package/dist/src/{utils-ICP7kLcY.js → utils-JaY9veb5.js} +2 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +43 -42
- package/dist/src/app/assets/index-COIipVf1.css +0 -1
- package/dist/src/app/assets/index-ClD64gUa.js +0 -428
- package/dist/src/app/assets/vendor-react-DHoaNLma.js +0 -4
- package/dist/src/cache-BJ31KhNT.cjs +0 -6
- package/dist/src/eval-DgiEH2xe.js +0 -16
- package/dist/src/evalResult-Boel8xoS.js +0 -13
- package/dist/src/evalResult-DGx1-Ela.js +0 -11
- package/dist/src/evalResult-DzRxYLDH.cjs +0 -11
- package/dist/src/evaluator-B4qKt437.js +0 -38
- package/dist/src/fetch-DwhKew9e.cjs +0 -4
- package/dist/src/graders-BpEqZT2Q.js +0 -34
- package/dist/src/graders-DTGI-hHE.cjs +0 -33
- package/dist/src/graders-DgMr-E4I.js +0 -36
- package/dist/src/providers-DVsoSup2.js +0 -34
- package/dist/src/providers-D_tJatIk.js +0 -32
- package/dist/src/providers-wpMPsMZ7.cjs +0 -32
- package/dist/src/server-BeqUuSvj.js +0 -6
- package/dist/src/server-CwQsQ6dl.cjs +0 -6
- package/dist/src/server-Dhbq83-d.js +0 -8
- package/dist/src/store-C7U17qcu.js +0 -6
- package/dist/src/store-CuDU_jDN.js +0 -7
- package/dist/src/store-awdDxuiD.cjs +0 -6
- package/dist/src/telemetry-ClcYTCOG.js +0 -8
- package/dist/src/telemetry-DEWuLu_s.js +0 -6
- package/dist/src/telemetry-tADmeX6E.cjs +0 -6
package/dist/src/main.js
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./chunk-DHDDz29n.js";
|
|
3
3
|
import { C as getEnvString, D as isNonInteractive, E as isCI, O as cliState_default, S as getEnvInt, b as getEnvBool, c as setLogLevel, d as extractFirstJsonObject, f as extractJsonObjects, h as orderKeys, i as isDebugEnabled, l as sanitizeObject, n as getLogLevel, o as logger_default, r as initializeRunLogging, s as setLogCallback, t as closeLogger, v as getConfigDirectoryPath, x as getEnvFloat, y as setConfigDirectoryPath } from "./logger-BotXmWKW.js";
|
|
4
|
-
import { A as getDefaultShareViewBaseUrl, I as HUMAN_ASSERTION_TYPE, M as getShareViewBaseUrl, O as TERMINAL_MAX_WIDTH, P as VERSION, S as CLOUD_PROVIDER_PREFIX, T as DEFAULT_QUERY_LIMIT, d as REQUEST_TIMEOUT_MS, h as isPromptfooSampleTarget, i as fetchWithTimeout, j as getShareApiBaseUrl, k as getDefaultPort, l as sleep, n as fetchWithProxy, r as fetchWithRetries, t as clearAgentCache, w as DEFAULT_MAX_CONCURRENCY } from "./fetch-
|
|
4
|
+
import { A as getDefaultShareViewBaseUrl, I as HUMAN_ASSERTION_TYPE, M as getShareViewBaseUrl, O as TERMINAL_MAX_WIDTH, P as VERSION, S as CLOUD_PROVIDER_PREFIX, T as DEFAULT_QUERY_LIMIT, d as REQUEST_TIMEOUT_MS, h as isPromptfooSampleTarget, i as fetchWithTimeout, j as getShareApiBaseUrl, k as getDefaultPort, l as sleep, n as fetchWithProxy, r as fetchWithRetries, t as clearAgentCache, w as DEFAULT_MAX_CONCURRENCY } from "./fetch-SRsE6Ctl.js";
|
|
5
5
|
import { t as invariant } from "./invariant-DT20jrBd.js";
|
|
6
|
-
import { a as getAuthor, d as EMAIL_OK_STATUS, l as promptForEmailUnverified, n as checkEmailStatusAndMaybeExit, o as getUserEmail, r as clearUserEmail, s as getUserId, t as checkEmailStatus, u as setUserEmail } from "./accounts-
|
|
7
|
-
import { a as readGlobalConfig, i as cloudConfig, r as CloudConfig, s as writeGlobalConfigPartial } from "./cloud-
|
|
8
|
-
import { n as TelemetryEventSchema, r as telemetry_default } from "./telemetry-
|
|
9
|
-
import { $ as HARM_PLUGINS, B as Severity, D as ADDITIONAL_STRATEGIES, E as isUuid, F as STRATEGY_COLLECTION_MAPPINGS, G as ALIASED_PLUGIN_MAPPINGS, I as getDefaultNFanout, J as BIAS_PLUGINS, K as ADDITIONAL_PLUGINS, M as DEFAULT_STRATEGIES, P as STRATEGY_COLLECTIONS, Q as FOUNDATION_PLUGINS, R as isFanoutStrategy, S as PluginConfigSchema, U as riskCategorySeverityMap, V as categoryAliases, W as subCategoryDescriptions, X as DEFAULT_PLUGINS, Y as DATASET_EXEMPT_PLUGINS, Z as FINANCIAL_PLUGINS, _ as PromptSchema, a as EvaluateOptionsSchema, at as MULTI_INPUT_VAR, b as ConversationMessageSchema, c as TestSuiteConfigSchema, dt as REMOTE_ONLY_PLUGIN_IDS, et as INSURANCE_PLUGINS, g as RedteamGenerateOptionsSchema, h as RedteamConfigSchema, i as EvalResultsFilterMode, it as MULTI_INPUT_EXCLUDED_PLUGINS, j as DEFAULT_MULTI_TURN_MAX_TURNS, k as ALL_STRATEGIES, l as TestSuiteSchema, lt as REDTEAM_MODEL, mt as UNALIGNED_PROVIDER_HARM_PLUGINS, n as BaseAssertionTypesSchema, nt as LLAMA_GUARD_REPLICATE_PROVIDER, o as OutputFileExtension, ot as PHARMACY_PLUGINS, p as isApiProvider, pt as TELECOM_PLUGINS, q as ALL_PLUGINS, r as CommandLineOptionsSchema, rt as MEDICAL_PLUGINS, s as ResultFailureReason, st as PII_PLUGINS, t as AssertionOrSetSchema, tt as LLAMA_GUARD_ENABLED_CATEGORIES, u as UnifiedConfigSchema, ut as REDTEAM_PROVIDER_HARM_PLUGINS, v as ProviderOptionsSchema, w as StrategyConfigSchema, x as PartialGenerationError, y as ProvidersSchema, z as isMultiTurnStrategy } from "./types-
|
|
10
|
-
import { a as openAuthBrowser, d as getRemoteHealthUrl, f as neverGenerateRemote, i as checkServerRunning, l as getRemoteGenerationUrl, m as shouldGenerateRemote, n as BrowserBehaviorNames, o as openBrowser, s as promptUser, t as BrowserBehavior } from "./server-
|
|
11
|
-
import { A as renderPrompt, C as extractVariablesFromJson, F as TokenUsageTracker, P as redteamProviderManager, R as PromptfooHarmfulCompletionProvider, S as extractPromptFromTags, T as getShortPluginId, _ as pluginMatchesStrategyTargets, _t as resolveTeamId, a as resolveProviderConfigs, at as getCloudDatabaseId, b as extractGoalFromPrompt, c as createTransformRequest, ct as getEvalConfigFromCloud, d as Strategies, f as loadStrategy, ft as getProviderFromCloud, g as retrieveMedia, gt as resolveTeamFromIdentifier, h as mediaExists, ht as makeRequest$1, i as resolveProvider, it as checkCloudPermissions, l as createTransformResponse, lt as getOrgContext, m as getMediaStorage, mt as isCloudProvider, n as loadApiProvider, o as MCPProvider, ot as getConfigFromCloud, p as validateStrategies, pt as getUserTeams, r as loadApiProviders, rt as canCreateTargets, s as HttpProvider, st as getDefaultTeam, t as getProviderIds, ut as getPluginSeverityOverridesFromCloud } from "./providers-
|
|
12
|
-
import { a as fetchWithCache, n as clearCache, r as disableCache, t as cache_exports } from "./cache-
|
|
13
|
-
import { i as isJavascriptFile } from "./fileExtensions-
|
|
14
|
-
import { A as filterRuntimeVars, C as extractVariablesFromTemplates, D as setupEnv, M as resultIsForTestCase, N as doesProviderRefMatch, O as deduplicateTestCases, P as getProviderDescription, S as extractVariablesFromTemplate, a as ComparisonEvalNotFoundError, b as renderEnvOnlyInObject, c as mergeComparisonTables, f as maybeLoadConfigFromExternalFile, i as writeOutput, j as getTestCaseDeduplicationKey, k as extractRuntimeVars, l as fetchCsvFromGoogleSheet, n as createOutputMetadata, o as evalTableToJson, p as maybeLoadFromExternalFile, r as writeMultipleOutputs, s as generateEvalCsv, t as printBorder, v as readFilters, w as getNunjucksEngine, y as readOutput } from "./util-
|
|
15
|
-
import { r as importModule, t as getDirectory } from "./esm-
|
|
16
|
-
import { r as runPython } from "./pythonUtils-
|
|
17
|
-
import "./transform-
|
|
18
|
-
import { A as BeavertailsPlugin, C as HarmbenchPlugin, D as DebugAccessPlugin, E as DivergentRepetitionPlugin, F as retryWithDeduplication, I as sampleArray, L as fetchHuggingFaceDataset, M as RedteamGraderBase, N as RedteamPluginBase, O as CrossSessionLeakPlugin, P as getCustomPolicies, S as ImitationPlugin, T as ExcessiveAgencyPlugin, _ as makeInlinePolicyIdSync, a as UnverifiableClaimsPlugin, b as OverreliancePlugin, c as ToolDiscoveryPlugin, ct as readProviderPromptMap, d as RbacPlugin, f as PromptExtractionPlugin, g as isValidPolicyObject, h as determinePolicyTypeFromId, i as VLGuardPlugin, it as getDefaultProviders, j as AegisPlugin, k as ContractPlugin, l as SqlInjectionPlugin, m as PolicyPlugin, o as UnsafeBenchPlugin, ot as processPrompts, p as PoliticsPlugin, r as VLSUPlugin, rt as doRemoteGrading, s as ToxicChatPlugin, st as readPrompts, t as GRADERS, u as ShellInjectionPlugin, v as PlinyPlugin, w as HallucinationPlugin, x as IntentPlugin, y as getPiiLeakTestsForCategory } from "./graders-
|
|
19
|
-
import { i as generateIdFromPrompt } from "./utils-
|
|
20
|
-
import { a as sha256, i as randomSequence, n as isBlobStorageEnabled } from "./extractor-
|
|
21
|
-
import "./genaiTracer-
|
|
22
|
-
import "./chat-
|
|
23
|
-
import { a as createEmptyTokenUsage, i as createEmptyAssertions, n as accumulateResponseTokenUsage, r as accumulateTokenUsage, t as accumulateAssertionTokenUsage } from "./tokenUsageUtils-
|
|
24
|
-
import "./transform-
|
|
25
|
-
import "./messages-
|
|
26
|
-
import "./util-
|
|
27
|
-
import "./responses-
|
|
28
|
-
import "./openai-
|
|
29
|
-
import "./util-
|
|
30
|
-
import "./completion-
|
|
31
|
-
import { n as getBlobUrl, t as getBlobByHash } from "./blobs-
|
|
32
|
-
import { a as evalResultsTable, c as evalsToPromptsTable, d as promptsTable, g as getDb, h as closeDbIfOpen, i as datasetsTable, l as evalsToTagsTable, n as blobReferencesTable, o as evalsTable, p as tagsTable, r as configsTable, s as evalsToDatasetsTable, t as blobAssetsTable, u as modelAuditsTable } from "./tables-
|
|
33
|
-
import { n as escapeRegExp, t as ellipsize } from "./text-
|
|
34
|
-
import { n as getTraceStore } from "./store-
|
|
35
|
-
import "./base-
|
|
36
|
-
import "./image-
|
|
37
|
-
import "./providerRegistry-
|
|
38
|
-
import "./rubyUtils-
|
|
39
|
-
import { c as readAssertions, l as renderMetricName, o as doesPromptRefMatch, s as assertions_default, t as evaluate$1, u as runAssertions } from "./evaluator-
|
|
40
|
-
import { n as setupSignalWatcher, t as readSignalEvalId } from "./signal-
|
|
41
|
-
import { a as createEvalId, n as EvalQueries, s as getEvalSummaries, t as Eval } from "./eval-
|
|
42
|
-
import { t as EvalResult } from "./evalResult-
|
|
43
|
-
import { t as formatDuration } from "./formatDuration-
|
|
44
|
-
import { n as shouldUseInkUI } from "./interactiveCheck-
|
|
6
|
+
import { a as getAuthor, d as EMAIL_OK_STATUS, l as promptForEmailUnverified, n as checkEmailStatusAndMaybeExit, o as getUserEmail, r as clearUserEmail, s as getUserId, t as checkEmailStatus, u as setUserEmail } from "./accounts-DHHiXsy6.js";
|
|
7
|
+
import { a as readGlobalConfig, i as cloudConfig, r as CloudConfig, s as writeGlobalConfigPartial } from "./cloud-BMbRVJFw.js";
|
|
8
|
+
import { n as TelemetryEventSchema, r as telemetry_default } from "./telemetry-BXyVqyAg.js";
|
|
9
|
+
import { $ as HARM_PLUGINS, B as Severity, D as ADDITIONAL_STRATEGIES, E as isUuid, F as STRATEGY_COLLECTION_MAPPINGS, G as ALIASED_PLUGIN_MAPPINGS, I as getDefaultNFanout, J as BIAS_PLUGINS, K as ADDITIONAL_PLUGINS, M as DEFAULT_STRATEGIES, P as STRATEGY_COLLECTIONS, Q as FOUNDATION_PLUGINS, R as isFanoutStrategy, S as PluginConfigSchema, U as riskCategorySeverityMap, V as categoryAliases, W as subCategoryDescriptions, X as DEFAULT_PLUGINS, Y as DATASET_EXEMPT_PLUGINS, Z as FINANCIAL_PLUGINS, _ as PromptSchema, a as EvaluateOptionsSchema, at as MULTI_INPUT_VAR, b as ConversationMessageSchema, c as TestSuiteConfigSchema, dt as REMOTE_ONLY_PLUGIN_IDS, et as INSURANCE_PLUGINS, g as RedteamGenerateOptionsSchema, h as RedteamConfigSchema, i as EvalResultsFilterMode, it as MULTI_INPUT_EXCLUDED_PLUGINS, j as DEFAULT_MULTI_TURN_MAX_TURNS, k as ALL_STRATEGIES, l as TestSuiteSchema, lt as REDTEAM_MODEL, mt as UNALIGNED_PROVIDER_HARM_PLUGINS, n as BaseAssertionTypesSchema, nt as LLAMA_GUARD_REPLICATE_PROVIDER, o as OutputFileExtension, ot as PHARMACY_PLUGINS, p as isApiProvider, pt as TELECOM_PLUGINS, q as ALL_PLUGINS, r as CommandLineOptionsSchema, rt as MEDICAL_PLUGINS, s as ResultFailureReason, st as PII_PLUGINS, t as AssertionOrSetSchema, tt as LLAMA_GUARD_ENABLED_CATEGORIES, u as UnifiedConfigSchema, ut as REDTEAM_PROVIDER_HARM_PLUGINS, v as ProviderOptionsSchema, w as StrategyConfigSchema, x as PartialGenerationError, y as ProvidersSchema, z as isMultiTurnStrategy } from "./types-t52w-XsS.js";
|
|
10
|
+
import { a as openAuthBrowser, d as getRemoteHealthUrl, f as neverGenerateRemote, i as checkServerRunning, l as getRemoteGenerationUrl, m as shouldGenerateRemote, n as BrowserBehaviorNames, o as openBrowser, s as promptUser, t as BrowserBehavior } from "./server-DWmZLfCy.js";
|
|
11
|
+
import { A as renderPrompt, C as extractVariablesFromJson, F as TokenUsageTracker, P as redteamProviderManager, R as PromptfooHarmfulCompletionProvider, S as extractPromptFromTags, T as getShortPluginId, _ as pluginMatchesStrategyTargets, _t as resolveTeamId, a as resolveProviderConfigs, at as getCloudDatabaseId, b as extractGoalFromPrompt, c as createTransformRequest, ct as getEvalConfigFromCloud, d as Strategies, f as loadStrategy, ft as getProviderFromCloud, g as retrieveMedia, gt as resolveTeamFromIdentifier, h as mediaExists, ht as makeRequest$1, i as resolveProvider, it as checkCloudPermissions, l as createTransformResponse, lt as getOrgContext, m as getMediaStorage, mt as isCloudProvider, n as loadApiProvider, o as MCPProvider, ot as getConfigFromCloud, p as validateStrategies, pt as getUserTeams, r as loadApiProviders, rt as canCreateTargets, s as HttpProvider, st as getDefaultTeam, t as getProviderIds, ut as getPluginSeverityOverridesFromCloud } from "./providers-BlEhY5mi.js";
|
|
12
|
+
import { a as fetchWithCache, n as clearCache, r as disableCache, t as cache_exports } from "./cache-mIszOnuz.js";
|
|
13
|
+
import { i as isJavascriptFile } from "./fileExtensions-BpuMmaFL.js";
|
|
14
|
+
import { A as filterRuntimeVars, C as extractVariablesFromTemplates, D as setupEnv, M as resultIsForTestCase, N as doesProviderRefMatch, O as deduplicateTestCases, P as getProviderDescription, S as extractVariablesFromTemplate, a as ComparisonEvalNotFoundError, b as renderEnvOnlyInObject, c as mergeComparisonTables, f as maybeLoadConfigFromExternalFile, i as writeOutput, j as getTestCaseDeduplicationKey, k as extractRuntimeVars, l as fetchCsvFromGoogleSheet, n as createOutputMetadata, o as evalTableToJson, p as maybeLoadFromExternalFile, r as writeMultipleOutputs, s as generateEvalCsv, t as printBorder, v as readFilters, w as getNunjucksEngine, y as readOutput } from "./util-BSh4a_Q8.js";
|
|
15
|
+
import { r as importModule, t as getDirectory } from "./esm-CYhseqj4.js";
|
|
16
|
+
import { r as runPython } from "./pythonUtils-r1uBuA0n.js";
|
|
17
|
+
import "./transform-CqTFr7KR.js";
|
|
18
|
+
import { A as BeavertailsPlugin, C as HarmbenchPlugin, D as DebugAccessPlugin, E as DivergentRepetitionPlugin, F as retryWithDeduplication, I as sampleArray, L as fetchHuggingFaceDataset, M as RedteamGraderBase, N as RedteamPluginBase, O as CrossSessionLeakPlugin, P as getCustomPolicies, S as ImitationPlugin, T as ExcessiveAgencyPlugin, _ as makeInlinePolicyIdSync, a as UnverifiableClaimsPlugin, b as OverreliancePlugin, c as ToolDiscoveryPlugin, ct as readProviderPromptMap, d as RbacPlugin, f as PromptExtractionPlugin, g as isValidPolicyObject, h as determinePolicyTypeFromId, i as VLGuardPlugin, it as getDefaultProviders, j as AegisPlugin, k as ContractPlugin, l as SqlInjectionPlugin, m as PolicyPlugin, o as UnsafeBenchPlugin, ot as processPrompts, p as PoliticsPlugin, r as VLSUPlugin, rt as doRemoteGrading, s as ToxicChatPlugin, st as readPrompts, t as GRADERS, u as ShellInjectionPlugin, v as PlinyPlugin, w as HallucinationPlugin, x as IntentPlugin, y as getPiiLeakTestsForCategory } from "./graders-BaMCwIKp.js";
|
|
19
|
+
import { i as generateIdFromPrompt } from "./utils-JaY9veb5.js";
|
|
20
|
+
import { a as sha256, i as randomSequence, n as isBlobStorageEnabled } from "./extractor-LSYjrhK0.js";
|
|
21
|
+
import "./genaiTracer-CqNnnXrE.js";
|
|
22
|
+
import "./chat-DIywASPG.js";
|
|
23
|
+
import { a as createEmptyTokenUsage, i as createEmptyAssertions, n as accumulateResponseTokenUsage, r as accumulateTokenUsage, t as accumulateAssertionTokenUsage } from "./tokenUsageUtils-DmZSD2eU.js";
|
|
24
|
+
import "./transform-CoP2bJ7P.js";
|
|
25
|
+
import "./messages-D61tPFQo.js";
|
|
26
|
+
import "./util-vjscpUzy.js";
|
|
27
|
+
import "./responses-TsdODUpm.js";
|
|
28
|
+
import "./openai-CT5fwbve.js";
|
|
29
|
+
import "./util-CUEt0Vum.js";
|
|
30
|
+
import "./completion-BBJ6zmG3.js";
|
|
31
|
+
import { n as getBlobUrl, t as getBlobByHash } from "./blobs-kt8v3UyH.js";
|
|
32
|
+
import { a as evalResultsTable, c as evalsToPromptsTable, d as promptsTable, g as getDb, h as closeDbIfOpen, i as datasetsTable, l as evalsToTagsTable, n as blobReferencesTable, o as evalsTable, p as tagsTable, r as configsTable, s as evalsToDatasetsTable, t as blobAssetsTable, u as modelAuditsTable } from "./tables-C7TT2XVn.js";
|
|
33
|
+
import { n as escapeRegExp, t as ellipsize } from "./text-DgMr_tiM.js";
|
|
34
|
+
import { n as getTraceStore } from "./store-D4gdn9ih.js";
|
|
35
|
+
import "./base-BaXmtXYp.js";
|
|
36
|
+
import "./image-mhAGP07h.js";
|
|
37
|
+
import "./providerRegistry-Xf0qdqGQ.js";
|
|
38
|
+
import "./rubyUtils-BtjlqyXt.js";
|
|
39
|
+
import { c as readAssertions, l as renderMetricName, o as doesPromptRefMatch, s as assertions_default, t as evaluate$1, u as runAssertions } from "./evaluator-3EJCMTs0.js";
|
|
40
|
+
import { n as setupSignalWatcher, t as readSignalEvalId } from "./signal-Bl32q42d.js";
|
|
41
|
+
import { a as createEvalId, n as EvalQueries, s as getEvalSummaries, t as Eval } from "./eval-CYrbG57o.js";
|
|
42
|
+
import { t as EvalResult } from "./evalResult-6JaUIStC.js";
|
|
43
|
+
import { t as formatDuration } from "./formatDuration-Doo0xq-z.js";
|
|
44
|
+
import { n as shouldUseInkUI } from "./interactiveCheck-DU-MAhp5.js";
|
|
45
45
|
import { fileURLToPath } from "node:url";
|
|
46
46
|
import fs, { realpathSync } from "node:fs";
|
|
47
47
|
import path, { resolve as resolve$1 } from "node:path";
|
|
@@ -114,7 +114,7 @@ function runCommand(program) {
|
|
|
114
114
|
hasGithubPr: !!cmdObj.githubPr,
|
|
115
115
|
hasGuidance: !!(cmdObj.guidance || cmdObj.guidanceFile)
|
|
116
116
|
});
|
|
117
|
-
const { executeScan } = await import("./scanner-
|
|
117
|
+
const { executeScan } = await import("./scanner-Dyw21Wg_.js");
|
|
118
118
|
await executeScan(repoPath, cmdObj);
|
|
119
119
|
});
|
|
120
120
|
}
|
|
@@ -2591,7 +2591,7 @@ try {
|
|
|
2591
2591
|
//#region src/util/inlineBlobsForShare.ts
|
|
2592
2592
|
const BLOB_URI_PREFIX = "promptfoo://blob/";
|
|
2593
2593
|
const BLOB_URI_REGEX = /promptfoo:\/\/blob\/([a-f0-9]{64})/gi;
|
|
2594
|
-
const BLOB_HASH_REGEX
|
|
2594
|
+
const BLOB_HASH_REGEX = /^[a-f0-9]{64}$/i;
|
|
2595
2595
|
const MAX_DEPTH = 8;
|
|
2596
2596
|
const MAX_STRING_LENGTH_TO_SCAN = 1e5;
|
|
2597
2597
|
function normalizeHash(hash) {
|
|
@@ -2614,7 +2614,7 @@ function extractHashFromBlobRef(value) {
|
|
|
2614
2614
|
const match = candidate.uri.match(BLOB_URI_REGEX);
|
|
2615
2615
|
return match?.[1] ? normalizeHash(match[1]) : null;
|
|
2616
2616
|
}
|
|
2617
|
-
if (candidate.hash && typeof candidate.hash === "string" && BLOB_HASH_REGEX
|
|
2617
|
+
if (candidate.hash && typeof candidate.hash === "string" && BLOB_HASH_REGEX.test(candidate.hash) && typeof candidate.mimeType === "string") return normalizeHash(candidate.hash);
|
|
2618
2618
|
return null;
|
|
2619
2619
|
}
|
|
2620
2620
|
function collectBlobHashes(value, hashes, visited, depth) {
|
|
@@ -3275,11 +3275,31 @@ function shouldShareResults(opts) {
|
|
|
3275
3275
|
* ```
|
|
3276
3276
|
*/
|
|
3277
3277
|
function generateEvalSummary(params) {
|
|
3278
|
-
const { evalId, isRedteam, writeToDatabase, shareableUrl, wantsToShare, hasExplicitDisable, cloudEnabled, activelySharing = false, tokenUsage, successes, failures, errors, duration, maxConcurrency, tracker } = params;
|
|
3278
|
+
const { evalId, isRedteam, writeToDatabase, shareableUrl, wantsToShare, hasExplicitDisable, cloudEnabled, activelySharing = false, tokenUsage, successes, failures, errors, duration, maxConcurrency, tracker, targetErrorStatus } = params;
|
|
3279
3279
|
const lines = [];
|
|
3280
3280
|
const completionType = isRedteam ? "Red team" : "Eval";
|
|
3281
|
-
const
|
|
3281
|
+
const wasAborted = targetErrorStatus != null;
|
|
3282
|
+
let completionMessage;
|
|
3283
|
+
if (wasAborted) {
|
|
3284
|
+
completionMessage = `${chalk.red("✗")} ${completionType} aborted`;
|
|
3285
|
+
if (writeToDatabase) completionMessage += ` (ID: ${chalk.cyan(evalId)})`;
|
|
3286
|
+
} else if (writeToDatabase && shareableUrl) completionMessage = `${chalk.green("✓")} ${completionType} complete: ${shareableUrl}`;
|
|
3287
|
+
else if (writeToDatabase && activelySharing) completionMessage = `${chalk.green("✓")} ${completionType} complete`;
|
|
3288
|
+
else if (writeToDatabase) completionMessage = `${chalk.green("✓")} ${completionType} complete (ID: ${chalk.cyan(evalId)})`;
|
|
3289
|
+
else completionMessage = `${chalk.green("✓")} ${completionType} complete`;
|
|
3282
3290
|
lines.push(completionMessage);
|
|
3291
|
+
if (wasAborted && targetErrorStatus != null) {
|
|
3292
|
+
lines.push("");
|
|
3293
|
+
lines.push(chalk.red.bold("Scan stopped: Target is unavailable and will not recover on retry."));
|
|
3294
|
+
lines.push(chalk.red(` Target returned HTTP ${targetErrorStatus}`));
|
|
3295
|
+
lines.push("");
|
|
3296
|
+
lines.push(chalk.yellow("Possible causes:"));
|
|
3297
|
+
lines.push(chalk.yellow(" • Invalid API key or authentication (401/403)"));
|
|
3298
|
+
lines.push(chalk.yellow(" • Target endpoint does not exist (404)"));
|
|
3299
|
+
lines.push(chalk.yellow(" • Server does not support the request (501)"));
|
|
3300
|
+
lines.push("");
|
|
3301
|
+
lines.push(chalk.cyan("To fix: Check your target configuration and credentials."));
|
|
3302
|
+
}
|
|
3283
3303
|
if (writeToDatabase && !shareableUrl && !wantsToShare && !activelySharing) {
|
|
3284
3304
|
lines.push("");
|
|
3285
3305
|
lines.push(`» View results: ${chalk.green.bold("promptfoo view")}`);
|
|
@@ -4305,6 +4325,7 @@ async function doEval(cmdObj, defaultConfig, defaultConfigPath, evaluateOptions)
|
|
|
4305
4325
|
const isRedteam = Boolean(config.redteam);
|
|
4306
4326
|
const duration = Math.round((Date.now() - startTime) / 1e3);
|
|
4307
4327
|
const tracker = TokenUsageTracker.getInstance();
|
|
4328
|
+
const targetErrorStatus = await evalRecord.findTargetErrorStatus();
|
|
4308
4329
|
const summaryLines = generateEvalSummary({
|
|
4309
4330
|
evalId: evalRecord.id,
|
|
4310
4331
|
isRedteam,
|
|
@@ -4320,7 +4341,8 @@ async function doEval(cmdObj, defaultConfig, defaultConfigPath, evaluateOptions)
|
|
|
4320
4341
|
errors,
|
|
4321
4342
|
duration,
|
|
4322
4343
|
maxConcurrency,
|
|
4323
|
-
tracker
|
|
4344
|
+
tracker,
|
|
4345
|
+
targetErrorStatus
|
|
4324
4346
|
});
|
|
4325
4347
|
if (cmdObj.write && wantsToShare && !canShareEval) {
|
|
4326
4348
|
logger_default.info(summaryLines[0]);
|
|
@@ -4375,7 +4397,7 @@ async function doEval(cmdObj, defaultConfig, defaultConfigPath, evaluateOptions)
|
|
|
4375
4397
|
if (initialization) {
|
|
4376
4398
|
const configPaths = (cmdObj.config || [defaultConfigPath]).filter(Boolean);
|
|
4377
4399
|
if (!configPaths.length) {
|
|
4378
|
-
logger_default.error(
|
|
4400
|
+
logger_default.error(`Could not locate config file(s) to watch. Pass --config path/to/promptfooconfig.yaml or run from a directory containing promptfooconfig.{${DEFAULT_CONFIG_EXTENSIONS.join(",")}}.`);
|
|
4379
4401
|
process.exitCode = 1;
|
|
4380
4402
|
return ret;
|
|
4381
4403
|
}
|
|
@@ -4464,7 +4486,9 @@ function evalCommand(program, defaultConfig, defaultConfigPath) {
|
|
|
4464
4486
|
evalCmd.help();
|
|
4465
4487
|
return;
|
|
4466
4488
|
}
|
|
4467
|
-
logger_default.
|
|
4489
|
+
logger_default.error(`Unknown command: ${command.args[0]}. Did you mean -c ${command.args[0]}?`);
|
|
4490
|
+
process.exitCode = 1;
|
|
4491
|
+
return;
|
|
4468
4492
|
}
|
|
4469
4493
|
if (validatedOpts.help) {
|
|
4470
4494
|
evalCmd.help();
|
|
@@ -4676,21 +4700,97 @@ async function checkRemoteHealth(url) {
|
|
|
4676
4700
|
}
|
|
4677
4701
|
}
|
|
4678
4702
|
|
|
4703
|
+
//#endregion
|
|
4704
|
+
//#region src/server/middleware/csrfProtection.ts
|
|
4705
|
+
const SAFE_METHODS = new Set([
|
|
4706
|
+
"GET",
|
|
4707
|
+
"HEAD",
|
|
4708
|
+
"OPTIONS"
|
|
4709
|
+
]);
|
|
4710
|
+
const KNOWN_LOCAL_HOSTS = new Set([
|
|
4711
|
+
"localhost",
|
|
4712
|
+
"127.0.0.1",
|
|
4713
|
+
"[::1]",
|
|
4714
|
+
"::1",
|
|
4715
|
+
"local.promptfoo.app"
|
|
4716
|
+
]);
|
|
4717
|
+
function isLocalHost(hostname) {
|
|
4718
|
+
return KNOWN_LOCAL_HOSTS.has(hostname);
|
|
4719
|
+
}
|
|
4720
|
+
function getAllowedOrigins() {
|
|
4721
|
+
const envOrigins = getEnvString("PROMPTFOO_CSRF_ALLOWED_ORIGINS", "");
|
|
4722
|
+
if (!envOrigins) return /* @__PURE__ */ new Set();
|
|
4723
|
+
return new Set(envOrigins.split(",").map((o) => o.trim()).filter(Boolean));
|
|
4724
|
+
}
|
|
4725
|
+
function stripPort(host) {
|
|
4726
|
+
return host.replace(/:\d+$/, "");
|
|
4727
|
+
}
|
|
4728
|
+
function isAllowedCrossSite(origin, host) {
|
|
4729
|
+
try {
|
|
4730
|
+
const originHostname = new URL(origin).hostname;
|
|
4731
|
+
const targetHostname = stripPort(host);
|
|
4732
|
+
if (isLocalHost(originHostname) && isLocalHost(targetHostname)) return true;
|
|
4733
|
+
} catch {
|
|
4734
|
+
return false;
|
|
4735
|
+
}
|
|
4736
|
+
return getAllowedOrigins().has(origin);
|
|
4737
|
+
}
|
|
4738
|
+
function csrfProtection(req, res, next) {
|
|
4739
|
+
if (SAFE_METHODS.has(req.method)) return next();
|
|
4740
|
+
const secFetchSite = req.headers["sec-fetch-site"];
|
|
4741
|
+
const origin = req.headers["origin"];
|
|
4742
|
+
const host = req.headers.host || "";
|
|
4743
|
+
if (secFetchSite) {
|
|
4744
|
+
if (secFetchSite !== "cross-site") return next();
|
|
4745
|
+
if (origin && isAllowedCrossSite(origin, host)) return next();
|
|
4746
|
+
logger_default.warn("[CSRF] Blocked cross-site request", {
|
|
4747
|
+
method: req.method,
|
|
4748
|
+
path: req.path,
|
|
4749
|
+
origin,
|
|
4750
|
+
host,
|
|
4751
|
+
secFetchSite
|
|
4752
|
+
});
|
|
4753
|
+
res.status(403).json({ error: "Cross-site requests are not allowed" });
|
|
4754
|
+
return;
|
|
4755
|
+
}
|
|
4756
|
+
if (origin) {
|
|
4757
|
+
try {
|
|
4758
|
+
if (new URL(origin).hostname === stripPort(host)) return next();
|
|
4759
|
+
if (isAllowedCrossSite(origin, host)) return next();
|
|
4760
|
+
} catch {}
|
|
4761
|
+
logger_default.warn("[CSRF] Blocked cross-origin request", {
|
|
4762
|
+
method: req.method,
|
|
4763
|
+
path: req.path,
|
|
4764
|
+
origin,
|
|
4765
|
+
host
|
|
4766
|
+
});
|
|
4767
|
+
res.status(403).json({ error: "Cross-origin requests are not allowed" });
|
|
4768
|
+
return;
|
|
4769
|
+
}
|
|
4770
|
+
return next();
|
|
4771
|
+
}
|
|
4772
|
+
|
|
4773
|
+
//#endregion
|
|
4774
|
+
//#region src/types/api/blobs.ts
|
|
4775
|
+
const GetBlobParamsSchema = z.object({ hash: z.string().regex(/^[a-f0-9]{64}$/i, "Invalid blob hash") });
|
|
4776
|
+
/** Grouped schemas for server-side validation. */
|
|
4777
|
+
const BlobsSchemas = { Get: { Params: GetBlobParamsSchema } };
|
|
4778
|
+
|
|
4679
4779
|
//#endregion
|
|
4680
4780
|
//#region src/server/routes/blobs.ts
|
|
4681
4781
|
const blobsRouter = express.Router();
|
|
4682
|
-
const BLOB_HASH_REGEX = /^[a-f0-9]{64}$/i;
|
|
4683
4782
|
const SAFE_MIME_TYPE_REGEX = /^[a-z]+\/[a-z0-9_+-]+$/i;
|
|
4684
4783
|
blobsRouter.get("/:hash", async (req, res) => {
|
|
4685
4784
|
if (!isBlobStorageEnabled()) {
|
|
4686
4785
|
res.status(404).json({ error: "Blob storage disabled" });
|
|
4687
4786
|
return;
|
|
4688
4787
|
}
|
|
4689
|
-
const
|
|
4690
|
-
if (!
|
|
4691
|
-
res.status(400).json({ error:
|
|
4788
|
+
const paramsResult = BlobsSchemas.Get.Params.safeParse(req.params);
|
|
4789
|
+
if (!paramsResult.success) {
|
|
4790
|
+
res.status(400).json({ error: z.prettifyError(paramsResult.error) });
|
|
4692
4791
|
return;
|
|
4693
4792
|
}
|
|
4793
|
+
const { hash } = paramsResult.data;
|
|
4694
4794
|
const db = getDb();
|
|
4695
4795
|
const asset = db.select({
|
|
4696
4796
|
hash: blobAssetsTable.hash,
|
|
@@ -4736,14 +4836,75 @@ blobsRouter.get("/:hash", async (req, res) => {
|
|
|
4736
4836
|
}
|
|
4737
4837
|
});
|
|
4738
4838
|
|
|
4839
|
+
//#endregion
|
|
4840
|
+
//#region src/types/api/common.ts
|
|
4841
|
+
/** Standard email validation schema. */
|
|
4842
|
+
const EmailSchema = z.string().email();
|
|
4843
|
+
/** Response containing a single message field. */
|
|
4844
|
+
const MessageResponseSchema = z.object({ message: z.string() });
|
|
4845
|
+
/** Timestamp that can be either an ISO string or Unix epoch number. */
|
|
4846
|
+
const TimestampSchema = z.union([z.string(), z.number()]);
|
|
4847
|
+
|
|
4848
|
+
//#endregion
|
|
4849
|
+
//#region src/types/api/configs.ts
|
|
4850
|
+
/** Base config fields shared across list and detail responses. */
|
|
4851
|
+
const BaseConfigSummarySchema = z.object({
|
|
4852
|
+
id: z.string(),
|
|
4853
|
+
name: z.string(),
|
|
4854
|
+
createdAt: TimestampSchema,
|
|
4855
|
+
updatedAt: TimestampSchema
|
|
4856
|
+
});
|
|
4857
|
+
const ConfigSummarySchema = BaseConfigSummarySchema.extend({ type: z.string() });
|
|
4858
|
+
const ListConfigsQuerySchema = z.object({ type: z.string().min(1).optional() });
|
|
4859
|
+
const ListConfigsResponseSchema = z.object({ configs: z.array(ConfigSummarySchema) });
|
|
4860
|
+
const CreateConfigRequestSchema = z.object({
|
|
4861
|
+
name: z.string().min(1),
|
|
4862
|
+
type: z.string().min(1),
|
|
4863
|
+
config: z.unknown().refine((v) => v != null, { message: "config is required" })
|
|
4864
|
+
});
|
|
4865
|
+
const CreateConfigResponseSchema = z.object({
|
|
4866
|
+
id: z.string(),
|
|
4867
|
+
createdAt: TimestampSchema
|
|
4868
|
+
});
|
|
4869
|
+
const ListConfigsByTypeParamsSchema = z.object({ type: z.string().min(1) });
|
|
4870
|
+
const ListConfigsByTypeResponseSchema = z.object({ configs: z.array(BaseConfigSummarySchema) });
|
|
4871
|
+
const GetConfigParamsSchema = z.object({
|
|
4872
|
+
type: z.string().min(1),
|
|
4873
|
+
id: z.string().min(1)
|
|
4874
|
+
});
|
|
4875
|
+
const GetConfigResponseSchema = ConfigSummarySchema.extend({ config: z.unknown() }).passthrough();
|
|
4876
|
+
/** Grouped schemas for server-side validation. */
|
|
4877
|
+
const ConfigSchemas = {
|
|
4878
|
+
List: {
|
|
4879
|
+
Query: ListConfigsQuerySchema,
|
|
4880
|
+
Response: ListConfigsResponseSchema
|
|
4881
|
+
},
|
|
4882
|
+
Create: {
|
|
4883
|
+
Request: CreateConfigRequestSchema,
|
|
4884
|
+
Response: CreateConfigResponseSchema
|
|
4885
|
+
},
|
|
4886
|
+
ListByType: {
|
|
4887
|
+
Params: ListConfigsByTypeParamsSchema,
|
|
4888
|
+
Response: ListConfigsByTypeResponseSchema
|
|
4889
|
+
},
|
|
4890
|
+
Get: {
|
|
4891
|
+
Params: GetConfigParamsSchema,
|
|
4892
|
+
Response: GetConfigResponseSchema
|
|
4893
|
+
}
|
|
4894
|
+
};
|
|
4895
|
+
|
|
4739
4896
|
//#endregion
|
|
4740
4897
|
//#region src/server/routes/configs.ts
|
|
4741
4898
|
const configsRouter = Router();
|
|
4742
4899
|
configsRouter.get("/", async (req, res) => {
|
|
4743
|
-
const
|
|
4900
|
+
const queryResult = ConfigSchemas.List.Query.safeParse(req.query);
|
|
4901
|
+
if (!queryResult.success) {
|
|
4902
|
+
res.status(400).json({ error: z.prettifyError(queryResult.error) });
|
|
4903
|
+
return;
|
|
4904
|
+
}
|
|
4744
4905
|
try {
|
|
4745
|
-
const type =
|
|
4746
|
-
const query =
|
|
4906
|
+
const { type } = queryResult.data;
|
|
4907
|
+
const query = (await getDb()).select({
|
|
4747
4908
|
id: configsTable.id,
|
|
4748
4909
|
name: configsTable.name,
|
|
4749
4910
|
createdAt: configsTable.createdAt,
|
|
@@ -4753,18 +4914,22 @@ configsRouter.get("/", async (req, res) => {
|
|
|
4753
4914
|
if (type) query.where(eq(configsTable.type, type));
|
|
4754
4915
|
const configs = await query;
|
|
4755
4916
|
logger_default.info(`Loaded ${configs.length} configs${type ? ` of type ${type}` : ""}`);
|
|
4756
|
-
res.json({ configs });
|
|
4917
|
+
res.json(ConfigSchemas.List.Response.parse({ configs }));
|
|
4757
4918
|
} catch (error) {
|
|
4758
4919
|
logger_default.error(`Error fetching configs: ${error}`);
|
|
4759
4920
|
res.status(500).json({ error: "Failed to fetch configs" });
|
|
4760
4921
|
}
|
|
4761
4922
|
});
|
|
4762
4923
|
configsRouter.post("/", async (req, res) => {
|
|
4763
|
-
const
|
|
4924
|
+
const bodyResult = ConfigSchemas.Create.Request.safeParse(req.body);
|
|
4925
|
+
if (!bodyResult.success) {
|
|
4926
|
+
res.status(400).json({ error: z.prettifyError(bodyResult.error) });
|
|
4927
|
+
return;
|
|
4928
|
+
}
|
|
4764
4929
|
try {
|
|
4765
|
-
const { name, type, config } =
|
|
4930
|
+
const { name, type, config } = bodyResult.data;
|
|
4766
4931
|
const id = crypto.randomUUID();
|
|
4767
|
-
const [result] = await
|
|
4932
|
+
const [result] = await (await getDb()).insert(configsTable).values({
|
|
4768
4933
|
id,
|
|
4769
4934
|
name,
|
|
4770
4935
|
type,
|
|
@@ -4774,41 +4939,48 @@ configsRouter.post("/", async (req, res) => {
|
|
|
4774
4939
|
createdAt: configsTable.createdAt
|
|
4775
4940
|
});
|
|
4776
4941
|
logger_default.info(`Saved config ${id} of type ${type}`);
|
|
4777
|
-
res.json(result);
|
|
4942
|
+
res.json(ConfigSchemas.Create.Response.parse(result));
|
|
4778
4943
|
} catch (error) {
|
|
4779
4944
|
logger_default.error(`Error saving config: ${error}`);
|
|
4780
4945
|
res.status(500).json({ error: "Failed to save config" });
|
|
4781
4946
|
}
|
|
4782
4947
|
});
|
|
4783
4948
|
configsRouter.get("/:type", async (req, res) => {
|
|
4784
|
-
const
|
|
4785
|
-
|
|
4949
|
+
const paramsResult = ConfigSchemas.ListByType.Params.safeParse(req.params);
|
|
4950
|
+
if (!paramsResult.success) {
|
|
4951
|
+
res.status(400).json({ error: z.prettifyError(paramsResult.error) });
|
|
4952
|
+
return;
|
|
4953
|
+
}
|
|
4786
4954
|
try {
|
|
4787
|
-
const
|
|
4955
|
+
const { type } = paramsResult.data;
|
|
4956
|
+
const configs = await (await getDb()).select({
|
|
4788
4957
|
id: configsTable.id,
|
|
4789
4958
|
name: configsTable.name,
|
|
4790
4959
|
createdAt: configsTable.createdAt,
|
|
4791
4960
|
updatedAt: configsTable.updatedAt
|
|
4792
4961
|
}).from(configsTable).where(eq(configsTable.type, type)).orderBy(configsTable.updatedAt);
|
|
4793
4962
|
logger_default.info(`Loaded ${configs.length} configs of type ${type}`);
|
|
4794
|
-
res.json({ configs });
|
|
4963
|
+
res.json(ConfigSchemas.ListByType.Response.parse({ configs }));
|
|
4795
4964
|
} catch (error) {
|
|
4796
4965
|
logger_default.error(`Error fetching configs: ${error}`);
|
|
4797
4966
|
res.status(500).json({ error: "Failed to fetch configs" });
|
|
4798
4967
|
}
|
|
4799
4968
|
});
|
|
4800
4969
|
configsRouter.get("/:type/:id", async (req, res) => {
|
|
4801
|
-
const
|
|
4802
|
-
|
|
4803
|
-
|
|
4970
|
+
const paramsResult = ConfigSchemas.Get.Params.safeParse(req.params);
|
|
4971
|
+
if (!paramsResult.success) {
|
|
4972
|
+
res.status(400).json({ error: z.prettifyError(paramsResult.error) });
|
|
4973
|
+
return;
|
|
4974
|
+
}
|
|
4804
4975
|
try {
|
|
4805
|
-
const
|
|
4806
|
-
|
|
4807
|
-
if (!config
|
|
4976
|
+
const { type, id } = paramsResult.data;
|
|
4977
|
+
const [config] = await (await getDb()).select().from(configsTable).where(and(eq(configsTable.type, type), eq(configsTable.id, id))).limit(1);
|
|
4978
|
+
if (!config) {
|
|
4808
4979
|
res.status(404).json({ error: "Config not found" });
|
|
4809
4980
|
return;
|
|
4810
4981
|
}
|
|
4811
|
-
|
|
4982
|
+
logger_default.info(`Loaded config ${id} of type ${type}`);
|
|
4983
|
+
res.json(ConfigSchemas.Get.Response.parse(config));
|
|
4812
4984
|
} catch (error) {
|
|
4813
4985
|
logger_default.error(`Error fetching config: ${error}`);
|
|
4814
4986
|
res.status(500).json({ error: "Failed to fetch config" });
|
|
@@ -6352,6 +6524,7 @@ async function synthesize$1({ abortSignal, delay, entities: entitiesOverride, in
|
|
|
6352
6524
|
seen.add(key);
|
|
6353
6525
|
return true;
|
|
6354
6526
|
});
|
|
6527
|
+
const needsGoalExtraction = strategies.some((s) => Strategies.find((def) => def.id === s.id)?.requiresGoalExtraction);
|
|
6355
6528
|
await validateStrategies(strategies);
|
|
6356
6529
|
await validateSharpDependency(strategies, plugins);
|
|
6357
6530
|
const redteamProvider = await redteamProviderManager.getProvider({ provider });
|
|
@@ -6543,13 +6716,15 @@ async function synthesize$1({ abortSignal, delay, entities: entitiesOverride, in
|
|
|
6543
6716
|
if (!Array.isArray(allPluginTests) || allPluginTests.length === 0) logger_default.warn(`Failed to generate tests for ${plugin.id}`);
|
|
6544
6717
|
else {
|
|
6545
6718
|
const testCasesWithMetadata = allPluginTests;
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
const
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6719
|
+
if (needsGoalExtraction) {
|
|
6720
|
+
logger_default.debug(`Extracting goal for ${testCasesWithMetadata.length} tests from ${plugin.id}...`);
|
|
6721
|
+
for (const testCase of testCasesWithMetadata) {
|
|
6722
|
+
const promptVar = testCase.vars?.[injectVar];
|
|
6723
|
+
const prompt = Array.isArray(promptVar) ? promptVar[0] : String(promptVar);
|
|
6724
|
+
const policy = getPolicyText(testCase.metadata);
|
|
6725
|
+
const extractedGoal = await extractGoalFromPrompt(prompt, purpose, plugin.id, policy);
|
|
6726
|
+
testCase.metadata.goal = extractedGoal;
|
|
6727
|
+
}
|
|
6553
6728
|
}
|
|
6554
6729
|
testCases.push(...testCasesWithMetadata);
|
|
6555
6730
|
}
|
|
@@ -6584,13 +6759,15 @@ async function synthesize$1({ abortSignal, delay, entities: entitiesOverride, in
|
|
|
6584
6759
|
...t.metadata || {}
|
|
6585
6760
|
}
|
|
6586
6761
|
}));
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
const
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6762
|
+
if (needsGoalExtraction) {
|
|
6763
|
+
logger_default.debug(`Extracting goal for ${testCasesWithMetadata.length} custom tests from ${plugin.id}...`);
|
|
6764
|
+
for (const testCase of testCasesWithMetadata) {
|
|
6765
|
+
const promptVar = testCase.vars?.[injectVar];
|
|
6766
|
+
const prompt = Array.isArray(promptVar) ? promptVar[0] : String(promptVar);
|
|
6767
|
+
const policy = getPolicyText(testCase.metadata);
|
|
6768
|
+
const extractedGoal = await extractGoalFromPrompt(prompt, purpose, plugin.id, policy);
|
|
6769
|
+
testCase.metadata.goal = extractedGoal;
|
|
6770
|
+
}
|
|
6594
6771
|
}
|
|
6595
6772
|
testCases.push(...testCasesWithMetadata);
|
|
6596
6773
|
logger_default.debug(`Added ${customTests.length} custom test cases from ${plugin.id}`);
|
|
@@ -7368,7 +7545,7 @@ async function doRedteamRun(options) {
|
|
|
7368
7545
|
const evalMs = evalResult.evaluationDurationMs ?? 0;
|
|
7369
7546
|
logger_default.info(chalk.gray(`Total scan time: ${formatDuration(totalMs / 1e3)} (generation: ${formatDuration(generationDurationMs / 1e3)}, evaluation: ${formatDuration(evalMs / 1e3)})`));
|
|
7370
7547
|
}
|
|
7371
|
-
logger_default.info(chalk.green("\nRed team scan complete!"));
|
|
7548
|
+
if (evalResult ? await evalResult.findTargetErrorStatus() != null : false) {} else logger_default.info(chalk.green("\nRed team scan complete!"));
|
|
7372
7549
|
if (!evalResult?.shared) if (options.liveRedteamConfig) logger_default.info(chalk.blue(`To view the results, click the ${chalk.bold("View Report")} button or run ${chalk.bold(promptfooCommand("redteam report"))} on the command line.`));
|
|
7373
7550
|
else logger_default.info(chalk.blue(`To view the results, run ${chalk.bold(promptfooCommand("redteam report"))}`));
|
|
7374
7551
|
setLogCallback(null);
|
|
@@ -7477,13 +7654,6 @@ var src_default = {
|
|
|
7477
7654
|
redteam
|
|
7478
7655
|
};
|
|
7479
7656
|
|
|
7480
|
-
//#endregion
|
|
7481
|
-
//#region src/types/api/common.ts
|
|
7482
|
-
/** Standard email validation schema. */
|
|
7483
|
-
const EmailSchema = z.string().email();
|
|
7484
|
-
/** Response containing a single message field. */
|
|
7485
|
-
const MessageResponseSchema = z.object({ message: z.string() });
|
|
7486
|
-
|
|
7487
7657
|
//#endregion
|
|
7488
7658
|
//#region src/types/api/eval.ts
|
|
7489
7659
|
/** Eval ID parameter schema. */
|
|
@@ -7515,8 +7685,111 @@ const EvalTableQuerySchema = z.object({
|
|
|
7515
7685
|
filter: z.union([z.string(), z.array(z.string())]).transform((v) => Array.isArray(v) ? v : v ? [v] : []).prefault([]),
|
|
7516
7686
|
comparisonEvalIds: z.union([z.string(), z.array(z.string())]).transform((v) => Array.isArray(v) ? v : v ? [v] : []).prefault([])
|
|
7517
7687
|
});
|
|
7688
|
+
/**
|
|
7689
|
+
* Schema for creating a new evaluation job.
|
|
7690
|
+
* Based on EvaluateTestSuiteWithEvaluateOptions type.
|
|
7691
|
+
* Note: prompts must be an array for this endpoint (evaluate() expects array).
|
|
7692
|
+
*/
|
|
7693
|
+
const CreateJobRequestSchema = TestSuiteConfigSchema.extend({
|
|
7694
|
+
prompts: z.array(z.union([z.string(), z.record(z.string(), z.unknown())])),
|
|
7695
|
+
evaluateOptions: EvaluateOptionsSchema.optional()
|
|
7696
|
+
}).passthrough();
|
|
7697
|
+
const CreateJobResponseSchema = z.object({ id: z.string().uuid() });
|
|
7698
|
+
const GetJobParamsSchema = z.object({ id: z.string().uuid() });
|
|
7699
|
+
const GetJobResponseSchema = z.discriminatedUnion("status", [
|
|
7700
|
+
z.object({
|
|
7701
|
+
status: z.literal("in-progress"),
|
|
7702
|
+
progress: z.number(),
|
|
7703
|
+
total: z.number(),
|
|
7704
|
+
logs: z.array(z.string())
|
|
7705
|
+
}),
|
|
7706
|
+
z.object({
|
|
7707
|
+
status: z.literal("complete"),
|
|
7708
|
+
result: z.record(z.string(), z.unknown()).nullable(),
|
|
7709
|
+
evalId: z.string().nullable(),
|
|
7710
|
+
logs: z.array(z.string())
|
|
7711
|
+
}),
|
|
7712
|
+
z.object({
|
|
7713
|
+
status: z.literal("error"),
|
|
7714
|
+
logs: z.array(z.string())
|
|
7715
|
+
})
|
|
7716
|
+
]);
|
|
7717
|
+
const UpdateEvalParamsSchema = EvalIdParamSchema;
|
|
7718
|
+
/** Schema for EvaluateTable - permissive to allow complex nested structures. */
|
|
7719
|
+
const EvaluateTableSchema = z.object({
|
|
7720
|
+
head: z.object({
|
|
7721
|
+
prompts: z.array(z.record(z.string(), z.unknown())),
|
|
7722
|
+
vars: z.array(z.string())
|
|
7723
|
+
}),
|
|
7724
|
+
body: z.array(z.record(z.string(), z.unknown()))
|
|
7725
|
+
}).passthrough();
|
|
7726
|
+
const UpdateEvalRequestSchema = z.object({
|
|
7727
|
+
table: EvaluateTableSchema.optional(),
|
|
7728
|
+
config: z.record(z.string(), z.unknown()).optional()
|
|
7729
|
+
});
|
|
7730
|
+
const UpdateEvalResponseSchema = MessageResponseSchema;
|
|
7731
|
+
const AddResultsParamsSchema = EvalIdParamSchema;
|
|
7732
|
+
/** Schema for eval results with minimal required fields.
|
|
7733
|
+
* EvaluateResult has many optional fields, but these core fields are required
|
|
7734
|
+
* for the result to be usable. Using passthrough to preserve all extra fields.
|
|
7735
|
+
*/
|
|
7736
|
+
const AddResultsRequestSchema = z.array(z.object({
|
|
7737
|
+
promptIdx: z.number().int().nonnegative(),
|
|
7738
|
+
testIdx: z.number().int().nonnegative(),
|
|
7739
|
+
success: z.boolean(),
|
|
7740
|
+
score: z.number()
|
|
7741
|
+
}).passthrough());
|
|
7742
|
+
const ReplayRequestSchema = z.object({
|
|
7743
|
+
evaluationId: z.string().min(1),
|
|
7744
|
+
testIndex: z.number().int().nonnegative().optional(),
|
|
7745
|
+
prompt: z.string().min(1),
|
|
7746
|
+
variables: z.record(z.string(), z.unknown()).optional()
|
|
7747
|
+
});
|
|
7748
|
+
const ReplayResponseSchema = z.object({
|
|
7749
|
+
output: z.string(),
|
|
7750
|
+
error: z.string().nullable().optional(),
|
|
7751
|
+
response: z.record(z.string(), z.unknown()).optional()
|
|
7752
|
+
});
|
|
7753
|
+
const SubmitRatingParamsSchema = z.object({
|
|
7754
|
+
evalId: z.string().min(1),
|
|
7755
|
+
id: z.string().min(1)
|
|
7756
|
+
});
|
|
7757
|
+
/** Permissive grading result schema. */
|
|
7758
|
+
const SubmitRatingRequestSchema = z.object({
|
|
7759
|
+
pass: z.boolean(),
|
|
7760
|
+
score: z.number()
|
|
7761
|
+
}).passthrough();
|
|
7762
|
+
const SaveEvalRequestSchema = z.object({
|
|
7763
|
+
data: z.object({
|
|
7764
|
+
results: z.record(z.string(), z.unknown()),
|
|
7765
|
+
config: z.record(z.string(), z.unknown())
|
|
7766
|
+
}).passthrough().optional(),
|
|
7767
|
+
config: z.record(z.string(), z.unknown()).optional(),
|
|
7768
|
+
prompts: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
7769
|
+
results: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
7770
|
+
author: z.string().nullable().optional(),
|
|
7771
|
+
createdAt: z.union([z.string(), z.number()]).optional(),
|
|
7772
|
+
vars: z.array(z.string()).optional()
|
|
7773
|
+
}).passthrough();
|
|
7774
|
+
const SaveEvalResponseSchema = z.object({ id: z.string() });
|
|
7775
|
+
const DeleteEvalParamsSchema = EvalIdParamSchema;
|
|
7776
|
+
const DeleteEvalResponseSchema = MessageResponseSchema;
|
|
7777
|
+
const BulkDeleteEvalsRequestSchema = z.object({ ids: z.array(z.string().min(1)).min(1) });
|
|
7518
7778
|
/** Grouped schemas for server-side validation. */
|
|
7519
7779
|
const EvalSchemas = {
|
|
7780
|
+
CreateJob: {
|
|
7781
|
+
Request: CreateJobRequestSchema,
|
|
7782
|
+
Response: CreateJobResponseSchema
|
|
7783
|
+
},
|
|
7784
|
+
GetJob: {
|
|
7785
|
+
Params: GetJobParamsSchema,
|
|
7786
|
+
Response: GetJobResponseSchema
|
|
7787
|
+
},
|
|
7788
|
+
Update: {
|
|
7789
|
+
Params: UpdateEvalParamsSchema,
|
|
7790
|
+
Request: UpdateEvalRequestSchema,
|
|
7791
|
+
Response: UpdateEvalResponseSchema
|
|
7792
|
+
},
|
|
7520
7793
|
UpdateAuthor: {
|
|
7521
7794
|
Params: UpdateEvalAuthorParamsSchema,
|
|
7522
7795
|
Request: UpdateEvalAuthorRequestSchema,
|
|
@@ -7537,7 +7810,31 @@ const EvalSchemas = {
|
|
|
7537
7810
|
Request: CopyEvalRequestSchema,
|
|
7538
7811
|
Response: CopyEvalResponseSchema
|
|
7539
7812
|
},
|
|
7540
|
-
Table: {
|
|
7813
|
+
Table: {
|
|
7814
|
+
Params: EvalIdParamSchema,
|
|
7815
|
+
Query: EvalTableQuerySchema
|
|
7816
|
+
},
|
|
7817
|
+
AddResults: {
|
|
7818
|
+
Params: AddResultsParamsSchema,
|
|
7819
|
+
Request: AddResultsRequestSchema
|
|
7820
|
+
},
|
|
7821
|
+
Replay: {
|
|
7822
|
+
Request: ReplayRequestSchema,
|
|
7823
|
+
Response: ReplayResponseSchema
|
|
7824
|
+
},
|
|
7825
|
+
SubmitRating: {
|
|
7826
|
+
Params: SubmitRatingParamsSchema,
|
|
7827
|
+
Request: SubmitRatingRequestSchema
|
|
7828
|
+
},
|
|
7829
|
+
Save: {
|
|
7830
|
+
Request: SaveEvalRequestSchema,
|
|
7831
|
+
Response: SaveEvalResponseSchema
|
|
7832
|
+
},
|
|
7833
|
+
Delete: {
|
|
7834
|
+
Params: DeleteEvalParamsSchema,
|
|
7835
|
+
Response: DeleteEvalResponseSchema
|
|
7836
|
+
},
|
|
7837
|
+
BulkDelete: { Request: BulkDeleteEvalsRequestSchema }
|
|
7541
7838
|
};
|
|
7542
7839
|
|
|
7543
7840
|
//#endregion
|
|
@@ -7562,7 +7859,16 @@ function setDownloadHeaders(res, fileName, contentType) {
|
|
|
7562
7859
|
const evalRouter = Router();
|
|
7563
7860
|
const evalJobs = /* @__PURE__ */ new Map();
|
|
7564
7861
|
evalRouter.post("/job", (req, res) => {
|
|
7565
|
-
const
|
|
7862
|
+
const result = EvalSchemas.CreateJob.Request.safeParse(req.body);
|
|
7863
|
+
if (!result.success) {
|
|
7864
|
+
res.status(400).json({ error: z.prettifyError(result.error) });
|
|
7865
|
+
return;
|
|
7866
|
+
}
|
|
7867
|
+
const { evaluateOptions, providers: _validatedProviders, ...restData } = result.data;
|
|
7868
|
+
const testSuite = {
|
|
7869
|
+
...restData,
|
|
7870
|
+
providers: req.body.providers
|
|
7871
|
+
};
|
|
7566
7872
|
const id = crypto.randomUUID();
|
|
7567
7873
|
evalJobs.set(id, {
|
|
7568
7874
|
evalId: null,
|
|
@@ -7584,12 +7890,12 @@ evalRouter.post("/job", (req, res) => {
|
|
|
7584
7890
|
job.total = total;
|
|
7585
7891
|
console.log(`[${id}] ${progress}/${total}`);
|
|
7586
7892
|
}
|
|
7587
|
-
})).then(async (
|
|
7893
|
+
})).then(async (evalResult) => {
|
|
7588
7894
|
const job = evalJobs.get(id);
|
|
7589
7895
|
invariant(job, "Job not found");
|
|
7590
7896
|
job.status = "complete";
|
|
7591
|
-
job.result = await
|
|
7592
|
-
job.evalId =
|
|
7897
|
+
job.result = await evalResult.toEvaluateSummary();
|
|
7898
|
+
job.evalId = evalResult.id;
|
|
7593
7899
|
console.log(`[${id}] Complete`);
|
|
7594
7900
|
}).catch((error) => {
|
|
7595
7901
|
logger_default.error(dedent`Failed to eval tests:
|
|
@@ -7602,79 +7908,98 @@ evalRouter.post("/job", (req, res) => {
|
|
|
7602
7908
|
job.evalId = null;
|
|
7603
7909
|
job.logs = [String(error)];
|
|
7604
7910
|
});
|
|
7605
|
-
res.json({ id });
|
|
7911
|
+
res.json(EvalSchemas.CreateJob.Response.parse({ id }));
|
|
7606
7912
|
});
|
|
7607
7913
|
evalRouter.get("/job/:id", (req, res) => {
|
|
7608
|
-
const
|
|
7914
|
+
const paramsResult = EvalSchemas.GetJob.Params.safeParse(req.params);
|
|
7915
|
+
if (!paramsResult.success) {
|
|
7916
|
+
res.status(400).json({ error: z.prettifyError(paramsResult.error) });
|
|
7917
|
+
return;
|
|
7918
|
+
}
|
|
7919
|
+
const { id } = paramsResult.data;
|
|
7609
7920
|
const job = evalJobs.get(id);
|
|
7610
7921
|
if (!job) {
|
|
7611
7922
|
res.status(404).json({ error: "Job not found" });
|
|
7612
7923
|
return;
|
|
7613
7924
|
}
|
|
7614
|
-
if (job.status === "complete") res.json({
|
|
7925
|
+
if (job.status === "complete") res.json(EvalSchemas.GetJob.Response.parse({
|
|
7615
7926
|
status: "complete",
|
|
7616
7927
|
result: job.result,
|
|
7617
7928
|
evalId: job.evalId,
|
|
7618
7929
|
logs: job.logs
|
|
7619
|
-
});
|
|
7620
|
-
else if (job.status === "error") res.json({
|
|
7930
|
+
}));
|
|
7931
|
+
else if (job.status === "error") res.json(EvalSchemas.GetJob.Response.parse({
|
|
7621
7932
|
status: "error",
|
|
7622
7933
|
logs: job.logs
|
|
7623
|
-
});
|
|
7624
|
-
else res.json({
|
|
7934
|
+
}));
|
|
7935
|
+
else res.json(EvalSchemas.GetJob.Response.parse({
|
|
7625
7936
|
status: "in-progress",
|
|
7626
7937
|
progress: job.progress,
|
|
7627
7938
|
total: job.total,
|
|
7628
7939
|
logs: job.logs
|
|
7629
|
-
});
|
|
7940
|
+
}));
|
|
7630
7941
|
});
|
|
7631
7942
|
evalRouter.patch("/:id", async (req, res) => {
|
|
7632
|
-
const
|
|
7633
|
-
|
|
7634
|
-
|
|
7635
|
-
|
|
7943
|
+
const paramsResult = EvalSchemas.Update.Params.safeParse(req.params);
|
|
7944
|
+
if (!paramsResult.success) {
|
|
7945
|
+
res.status(400).json({ error: z.prettifyError(paramsResult.error) });
|
|
7946
|
+
return;
|
|
7947
|
+
}
|
|
7948
|
+
const bodyResult = EvalSchemas.Update.Request.safeParse(req.body);
|
|
7949
|
+
if (!bodyResult.success) {
|
|
7950
|
+
res.status(400).json({ error: z.prettifyError(bodyResult.error) });
|
|
7636
7951
|
return;
|
|
7637
7952
|
}
|
|
7953
|
+
const { id } = paramsResult.data;
|
|
7954
|
+
const { table, config } = bodyResult.data;
|
|
7638
7955
|
try {
|
|
7639
7956
|
await updateResult(id, config, table);
|
|
7640
|
-
res.json({ message: "Eval updated successfully" });
|
|
7957
|
+
res.json(EvalSchemas.Update.Response.parse({ message: "Eval updated successfully" }));
|
|
7641
7958
|
} catch {
|
|
7642
7959
|
res.status(500).json({ error: "Failed to update eval table" });
|
|
7643
7960
|
}
|
|
7644
7961
|
});
|
|
7645
7962
|
evalRouter.patch("/:id/author", async (req, res) => {
|
|
7963
|
+
const paramsResult = EvalSchemas.UpdateAuthor.Params.safeParse(req.params);
|
|
7964
|
+
if (!paramsResult.success) {
|
|
7965
|
+
res.status(400).json({ error: z.prettifyError(paramsResult.error) });
|
|
7966
|
+
return;
|
|
7967
|
+
}
|
|
7968
|
+
const bodyResult = EvalSchemas.UpdateAuthor.Request.safeParse(req.body);
|
|
7969
|
+
if (!bodyResult.success) {
|
|
7970
|
+
res.status(400).json({ error: z.prettifyError(bodyResult.error) });
|
|
7971
|
+
return;
|
|
7972
|
+
}
|
|
7973
|
+
const { id } = paramsResult.data;
|
|
7974
|
+
const { author } = bodyResult.data;
|
|
7646
7975
|
try {
|
|
7647
|
-
const { id } = EvalSchemas.UpdateAuthor.Params.parse(req.params);
|
|
7648
|
-
const { author } = EvalSchemas.UpdateAuthor.Request.parse(req.body);
|
|
7649
7976
|
const eval_ = await Eval.findById(id);
|
|
7650
7977
|
if (!eval_) {
|
|
7651
7978
|
res.status(404).json({ error: "Eval not found" });
|
|
7652
7979
|
return;
|
|
7653
7980
|
}
|
|
7654
|
-
if (!author) {
|
|
7655
|
-
res.status(400).json({ error: "No author provided" });
|
|
7656
|
-
return;
|
|
7657
|
-
}
|
|
7658
7981
|
eval_.author = author;
|
|
7659
7982
|
await eval_.save();
|
|
7660
7983
|
if (!getUserEmail()) setUserEmail(author);
|
|
7661
7984
|
res.json(EvalSchemas.UpdateAuthor.Response.parse({ message: "Author updated successfully" }));
|
|
7662
7985
|
} catch (error) {
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
logger_default.error(`Failed to update eval author: ${error}`);
|
|
7666
|
-
res.status(500).json({ error: "Failed to update eval author" });
|
|
7667
|
-
}
|
|
7986
|
+
logger_default.error(`Failed to update eval author: ${error}`);
|
|
7987
|
+
res.status(500).json({ error: "Failed to update eval author" });
|
|
7668
7988
|
}
|
|
7669
7989
|
});
|
|
7670
7990
|
const UNLIMITED_RESULTS = Number.MAX_SAFE_INTEGER;
|
|
7671
7991
|
evalRouter.get("/:id/table", async (req, res) => {
|
|
7672
|
-
const
|
|
7673
|
-
|
|
7992
|
+
const paramsResult = EvalSchemas.Table.Params.safeParse(req.params);
|
|
7993
|
+
if (!paramsResult.success) {
|
|
7994
|
+
res.status(400).json({ error: z.prettifyError(paramsResult.error) });
|
|
7995
|
+
return;
|
|
7996
|
+
}
|
|
7997
|
+
const queryResult = EvalSchemas.Table.Query.safeParse(req.query);
|
|
7674
7998
|
if (!queryResult.success) {
|
|
7675
7999
|
res.status(400).json({ error: z.prettifyError(queryResult.error) });
|
|
7676
8000
|
return;
|
|
7677
8001
|
}
|
|
8002
|
+
const { id } = paramsResult.data;
|
|
7678
8003
|
const { format, limit: baseLimit, offset: baseOffset, filterMode, search: searchText, filter: filters, comparisonEvalIds } = queryResult.data;
|
|
7679
8004
|
const limit = format ? UNLIMITED_RESULTS : baseLimit;
|
|
7680
8005
|
const offset = format ? 0 : baseOffset;
|
|
@@ -7785,9 +8110,19 @@ evalRouter.get("/:id/table", async (req, res) => {
|
|
|
7785
8110
|
});
|
|
7786
8111
|
});
|
|
7787
8112
|
evalRouter.get("/:id/metadata-keys", async (req, res) => {
|
|
8113
|
+
const paramsResult = EvalSchemas.MetadataKeys.Params.safeParse(req.params);
|
|
8114
|
+
if (!paramsResult.success) {
|
|
8115
|
+
res.status(400).json({ error: z.prettifyError(paramsResult.error) });
|
|
8116
|
+
return;
|
|
8117
|
+
}
|
|
8118
|
+
const queryResult = EvalSchemas.MetadataKeys.Query.safeParse(req.query);
|
|
8119
|
+
if (!queryResult.success) {
|
|
8120
|
+
res.status(400).json({ error: z.prettifyError(queryResult.error) });
|
|
8121
|
+
return;
|
|
8122
|
+
}
|
|
8123
|
+
const { id } = paramsResult.data;
|
|
8124
|
+
const { comparisonEvalIds = [] } = queryResult.data;
|
|
7788
8125
|
try {
|
|
7789
|
-
const { id } = EvalSchemas.MetadataKeys.Params.parse(req.params);
|
|
7790
|
-
const { comparisonEvalIds = [] } = EvalSchemas.MetadataKeys.Query.parse(req.query);
|
|
7791
8126
|
if (!await Eval.findById(id)) {
|
|
7792
8127
|
res.status(404).json({ error: "Eval not found" });
|
|
7793
8128
|
return;
|
|
@@ -7804,19 +8139,24 @@ evalRouter.get("/:id/metadata-keys", async (req, res) => {
|
|
|
7804
8139
|
const response = EvalSchemas.MetadataKeys.Response.parse({ keys });
|
|
7805
8140
|
res.json(response);
|
|
7806
8141
|
} catch (error) {
|
|
7807
|
-
if (error instanceof z.ZodError) {
|
|
7808
|
-
res.status(400).json({ error: z.prettifyError(error) });
|
|
7809
|
-
return;
|
|
7810
|
-
}
|
|
7811
|
-
const { id } = req.params;
|
|
7812
8142
|
logger_default.error(`Error fetching metadata keys for eval ${id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7813
8143
|
res.status(500).json({ error: "Failed to fetch metadata keys" });
|
|
7814
8144
|
}
|
|
7815
8145
|
});
|
|
7816
8146
|
evalRouter.get("/:id/metadata-values", async (req, res) => {
|
|
8147
|
+
const paramsResult = EvalSchemas.MetadataValues.Params.safeParse(req.params);
|
|
8148
|
+
if (!paramsResult.success) {
|
|
8149
|
+
res.status(400).json({ error: z.prettifyError(paramsResult.error) });
|
|
8150
|
+
return;
|
|
8151
|
+
}
|
|
8152
|
+
const queryResult = EvalSchemas.MetadataValues.Query.safeParse(req.query);
|
|
8153
|
+
if (!queryResult.success) {
|
|
8154
|
+
res.status(400).json({ error: z.prettifyError(queryResult.error) });
|
|
8155
|
+
return;
|
|
8156
|
+
}
|
|
8157
|
+
const { id } = paramsResult.data;
|
|
8158
|
+
const { key } = queryResult.data;
|
|
7817
8159
|
try {
|
|
7818
|
-
const { id } = EvalSchemas.MetadataValues.Params.parse(req.params);
|
|
7819
|
-
const { key } = EvalSchemas.MetadataValues.Query.parse(req.query);
|
|
7820
8160
|
if (!await Eval.findById(id)) {
|
|
7821
8161
|
res.status(404).json({ error: "Eval not found" });
|
|
7822
8162
|
return;
|
|
@@ -7825,22 +8165,23 @@ evalRouter.get("/:id/metadata-values", async (req, res) => {
|
|
|
7825
8165
|
const response = EvalSchemas.MetadataValues.Response.parse({ values });
|
|
7826
8166
|
res.json(response);
|
|
7827
8167
|
} catch (error) {
|
|
7828
|
-
if (error instanceof z.ZodError) {
|
|
7829
|
-
res.status(400).json({ error: z.prettifyError(error) });
|
|
7830
|
-
return;
|
|
7831
|
-
}
|
|
7832
|
-
const { id } = req.params;
|
|
7833
8168
|
logger_default.error(`Error fetching metadata values for eval ${id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7834
8169
|
res.status(500).json({ error: "Failed to fetch metadata values" });
|
|
7835
8170
|
}
|
|
7836
8171
|
});
|
|
7837
8172
|
evalRouter.post("/:id/results", async (req, res) => {
|
|
7838
|
-
const
|
|
7839
|
-
|
|
7840
|
-
|
|
7841
|
-
|
|
8173
|
+
const paramsResult = EvalSchemas.AddResults.Params.safeParse(req.params);
|
|
8174
|
+
if (!paramsResult.success) {
|
|
8175
|
+
res.status(400).json({ error: z.prettifyError(paramsResult.error) });
|
|
8176
|
+
return;
|
|
8177
|
+
}
|
|
8178
|
+
const bodyResult = EvalSchemas.AddResults.Request.safeParse(req.body);
|
|
8179
|
+
if (!bodyResult.success) {
|
|
8180
|
+
res.status(400).json({ error: z.prettifyError(bodyResult.error) });
|
|
7842
8181
|
return;
|
|
7843
8182
|
}
|
|
8183
|
+
const { id } = paramsResult.data;
|
|
8184
|
+
const results = bodyResult.data;
|
|
7844
8185
|
const eval_ = await Eval.findById(id);
|
|
7845
8186
|
if (!eval_) {
|
|
7846
8187
|
res.status(404).json({ error: "Eval not found" });
|
|
@@ -7856,11 +8197,12 @@ evalRouter.post("/:id/results", async (req, res) => {
|
|
|
7856
8197
|
res.status(204).send();
|
|
7857
8198
|
});
|
|
7858
8199
|
evalRouter.post("/replay", async (req, res) => {
|
|
7859
|
-
const
|
|
7860
|
-
if (!
|
|
7861
|
-
res.status(400).json({ error:
|
|
8200
|
+
const bodyResult = EvalSchemas.Replay.Request.safeParse(req.body);
|
|
8201
|
+
if (!bodyResult.success) {
|
|
8202
|
+
res.status(400).json({ error: z.prettifyError(bodyResult.error) });
|
|
7862
8203
|
return;
|
|
7863
8204
|
}
|
|
8205
|
+
const { evaluationId, testIndex, prompt, variables } = bodyResult.data;
|
|
7864
8206
|
try {
|
|
7865
8207
|
const eval_ = await Eval.findById(evaluationId);
|
|
7866
8208
|
if (!eval_) {
|
|
@@ -7878,7 +8220,7 @@ evalRouter.post("/replay", async (req, res) => {
|
|
|
7878
8220
|
res.status(400).json({ error: "No providers found in evaluation" });
|
|
7879
8221
|
return;
|
|
7880
8222
|
}
|
|
7881
|
-
providerConfig = providers[testIndex % providers.length];
|
|
8223
|
+
providerConfig = providers[(testIndex ?? 0) % providers.length];
|
|
7882
8224
|
} else if (typeof providers === "string" || typeof providers === "function") providerConfig = providers;
|
|
7883
8225
|
else providerConfig = providers;
|
|
7884
8226
|
const firstResult = (await (await src_default.evaluate({
|
|
@@ -7895,20 +8237,34 @@ evalRouter.post("/replay", async (req, res) => {
|
|
|
7895
8237
|
cache: false
|
|
7896
8238
|
})).toEvaluateSummary()).results[0];
|
|
7897
8239
|
let output = firstResult?.response?.output;
|
|
7898
|
-
if (
|
|
7899
|
-
|
|
7900
|
-
|
|
8240
|
+
if (output === void 0 && firstResult?.response?.raw) output = firstResult.response.raw;
|
|
8241
|
+
let serializedOutput;
|
|
8242
|
+
if (output === null || output === void 0) serializedOutput = "";
|
|
8243
|
+
else if (typeof output === "string") serializedOutput = output;
|
|
8244
|
+
else serializedOutput = JSON.stringify(output, null, 2);
|
|
8245
|
+
res.json(EvalSchemas.Replay.Response.parse({
|
|
8246
|
+
output: serializedOutput,
|
|
7901
8247
|
error: firstResult?.response?.error,
|
|
7902
8248
|
response: firstResult?.response
|
|
7903
|
-
});
|
|
8249
|
+
}));
|
|
7904
8250
|
} catch (error) {
|
|
7905
8251
|
logger_default.error(`Failed to replay evaluation: ${error}`);
|
|
7906
8252
|
res.status(500).json({ error: "Failed to replay evaluation" });
|
|
7907
8253
|
}
|
|
7908
8254
|
});
|
|
7909
8255
|
evalRouter.post("/:evalId/results/:id/rating", async (req, res) => {
|
|
7910
|
-
const
|
|
7911
|
-
|
|
8256
|
+
const paramsResult = EvalSchemas.SubmitRating.Params.safeParse(req.params);
|
|
8257
|
+
if (!paramsResult.success) {
|
|
8258
|
+
res.status(400).json({ error: z.prettifyError(paramsResult.error) });
|
|
8259
|
+
return;
|
|
8260
|
+
}
|
|
8261
|
+
const bodyResult = EvalSchemas.SubmitRating.Request.safeParse(req.body);
|
|
8262
|
+
if (!bodyResult.success) {
|
|
8263
|
+
res.status(400).json({ error: z.prettifyError(bodyResult.error) });
|
|
8264
|
+
return;
|
|
8265
|
+
}
|
|
8266
|
+
const { id } = paramsResult.data;
|
|
8267
|
+
const gradingResult = bodyResult.data;
|
|
7912
8268
|
const result = await EvalResult.findById(id);
|
|
7913
8269
|
invariant(result, "Result not found");
|
|
7914
8270
|
const eval_ = await Eval.findById(result.evalId);
|
|
@@ -7946,26 +8302,35 @@ evalRouter.post("/:evalId/results/:id/rating", async (req, res) => {
|
|
|
7946
8302
|
res.json(result);
|
|
7947
8303
|
});
|
|
7948
8304
|
evalRouter.post("/", async (req, res) => {
|
|
7949
|
-
const
|
|
8305
|
+
const bodyResult = EvalSchemas.Save.Request.safeParse(req.body);
|
|
8306
|
+
if (!bodyResult.success) {
|
|
8307
|
+
res.status(400).json({ error: z.prettifyError(bodyResult.error) });
|
|
8308
|
+
return;
|
|
8309
|
+
}
|
|
8310
|
+
const body = bodyResult.data;
|
|
7950
8311
|
try {
|
|
7951
8312
|
if (body.data) {
|
|
7952
8313
|
logger_default.debug("[POST /api/eval] Saving eval results (v3) to database");
|
|
7953
|
-
const
|
|
8314
|
+
const payload = body.data;
|
|
7954
8315
|
const id = await writeResultsToDatabase(payload.results, payload.config);
|
|
7955
|
-
res.json({ id });
|
|
8316
|
+
res.json(EvalSchemas.Save.Response.parse({ id }));
|
|
7956
8317
|
} else {
|
|
8318
|
+
if (!body.results || !body.config) {
|
|
8319
|
+
res.status(400).json({ error: "Missing required fields: results and config are required for v4 format" });
|
|
8320
|
+
return;
|
|
8321
|
+
}
|
|
7957
8322
|
const incEval = body;
|
|
7958
8323
|
logger_default.debug("[POST /api/eval] Saving eval results (v4) to database");
|
|
7959
8324
|
const eval_ = await Eval.create(incEval.config, incEval.prompts || [], {
|
|
7960
8325
|
author: incEval.author,
|
|
7961
|
-
createdAt: new Date(incEval.createdAt),
|
|
8326
|
+
createdAt: incEval.createdAt !== void 0 ? new Date(incEval.createdAt) : void 0,
|
|
7962
8327
|
results: incEval.results,
|
|
7963
8328
|
vars: incEval.vars
|
|
7964
8329
|
});
|
|
7965
8330
|
if (incEval.prompts) eval_.addPrompts(incEval.prompts);
|
|
7966
8331
|
logger_default.debug(`[POST /api/eval] Eval created with ID: ${eval_.id}`);
|
|
7967
8332
|
logger_default.debug(`[POST /api/eval] Saved ${incEval.results.length} results to eval ${eval_.id}`);
|
|
7968
|
-
res.json({ id: eval_.id });
|
|
8333
|
+
res.json(EvalSchemas.Save.Response.parse({ id: eval_.id }));
|
|
7969
8334
|
}
|
|
7970
8335
|
} catch (error) {
|
|
7971
8336
|
logger_default.error(dedent`Failed to write eval to database:
|
|
@@ -7975,10 +8340,15 @@ evalRouter.post("/", async (req, res) => {
|
|
|
7975
8340
|
}
|
|
7976
8341
|
});
|
|
7977
8342
|
evalRouter.delete("/:id", async (req, res) => {
|
|
7978
|
-
const
|
|
8343
|
+
const paramsResult = EvalSchemas.Delete.Params.safeParse(req.params);
|
|
8344
|
+
if (!paramsResult.success) {
|
|
8345
|
+
res.status(400).json({ error: z.prettifyError(paramsResult.error) });
|
|
8346
|
+
return;
|
|
8347
|
+
}
|
|
8348
|
+
const { id } = paramsResult.data;
|
|
7979
8349
|
try {
|
|
7980
8350
|
await deleteEval(id);
|
|
7981
|
-
res.json({ message: "Eval deleted successfully" });
|
|
8351
|
+
res.json(EvalSchemas.Delete.Response.parse({ message: "Eval deleted successfully" }));
|
|
7982
8352
|
} catch (error) {
|
|
7983
8353
|
logger_default.error("[DELETE /eval/:id] Failed to delete eval", {
|
|
7984
8354
|
evalId: id,
|
|
@@ -7995,11 +8365,12 @@ evalRouter.delete("/:id", async (req, res) => {
|
|
|
7995
8365
|
* Bulk delete evals.
|
|
7996
8366
|
*/
|
|
7997
8367
|
evalRouter.delete("/", (req, res) => {
|
|
7998
|
-
const
|
|
7999
|
-
if (!
|
|
8000
|
-
res.status(400).json({ error:
|
|
8368
|
+
const bodyResult = EvalSchemas.BulkDelete.Request.safeParse(req.body);
|
|
8369
|
+
if (!bodyResult.success) {
|
|
8370
|
+
res.status(400).json({ error: z.prettifyError(bodyResult.error) });
|
|
8001
8371
|
return;
|
|
8002
8372
|
}
|
|
8373
|
+
const { ids } = bodyResult.data;
|
|
8003
8374
|
try {
|
|
8004
8375
|
deleteEvals(ids);
|
|
8005
8376
|
res.status(204).send();
|
|
@@ -8011,9 +8382,19 @@ evalRouter.delete("/", (req, res) => {
|
|
|
8011
8382
|
* Copy an eval with all its results and relationships.
|
|
8012
8383
|
*/
|
|
8013
8384
|
evalRouter.post("/:id/copy", async (req, res) => {
|
|
8385
|
+
const paramsResult = EvalSchemas.Copy.Params.safeParse(req.params);
|
|
8386
|
+
if (!paramsResult.success) {
|
|
8387
|
+
res.status(400).json({ error: z.prettifyError(paramsResult.error) });
|
|
8388
|
+
return;
|
|
8389
|
+
}
|
|
8390
|
+
const bodyResult = EvalSchemas.Copy.Request.safeParse(req.body);
|
|
8391
|
+
if (!bodyResult.success) {
|
|
8392
|
+
res.status(400).json({ error: z.prettifyError(bodyResult.error) });
|
|
8393
|
+
return;
|
|
8394
|
+
}
|
|
8395
|
+
const { id } = paramsResult.data;
|
|
8396
|
+
const { description } = bodyResult.data;
|
|
8014
8397
|
try {
|
|
8015
|
-
const { id } = EvalSchemas.Copy.Params.parse(req.params);
|
|
8016
|
-
const { description } = EvalSchemas.Copy.Request.parse(req.body);
|
|
8017
8398
|
const sourceEval = await Eval.findById(id);
|
|
8018
8399
|
if (!sourceEval) {
|
|
8019
8400
|
res.status(404).json({ error: "Eval not found" });
|
|
@@ -8032,18 +8413,27 @@ evalRouter.post("/:id/copy", async (req, res) => {
|
|
|
8032
8413
|
});
|
|
8033
8414
|
res.status(201).json(response);
|
|
8034
8415
|
} catch (error) {
|
|
8035
|
-
if (error instanceof z.ZodError) {
|
|
8036
|
-
res.status(400).json({ error: z.prettifyError(error) });
|
|
8037
|
-
return;
|
|
8038
|
-
}
|
|
8039
8416
|
logger_default.error("Failed to copy eval", {
|
|
8040
8417
|
error,
|
|
8041
|
-
evalId:
|
|
8418
|
+
evalId: id
|
|
8042
8419
|
});
|
|
8043
8420
|
res.status(500).json({ error: "Failed to copy evaluation" });
|
|
8044
8421
|
}
|
|
8045
8422
|
});
|
|
8046
8423
|
|
|
8424
|
+
//#endregion
|
|
8425
|
+
//#region src/types/api/media.ts
|
|
8426
|
+
const MediaParamsSchema = z.object({
|
|
8427
|
+
type: z.enum([
|
|
8428
|
+
"audio",
|
|
8429
|
+
"image",
|
|
8430
|
+
"video"
|
|
8431
|
+
]),
|
|
8432
|
+
filename: z.string().regex(/^[a-f0-9]{12}\.[a-z0-9]+$/i, "Invalid media filename")
|
|
8433
|
+
});
|
|
8434
|
+
/** Grouped schemas for server-side validation. */
|
|
8435
|
+
const MediaSchemas = { Params: MediaParamsSchema };
|
|
8436
|
+
|
|
8047
8437
|
//#endregion
|
|
8048
8438
|
//#region src/server/routes/media.ts
|
|
8049
8439
|
/**
|
|
@@ -8052,15 +8442,6 @@ evalRouter.post("/:id/copy", async (req, res) => {
|
|
|
8052
8442
|
* Serves media files stored in the local filesystem storage.
|
|
8053
8443
|
*/
|
|
8054
8444
|
const mediaRouter = express.Router();
|
|
8055
|
-
const ALLOWED_MEDIA_TYPES = new Set([
|
|
8056
|
-
"audio",
|
|
8057
|
-
"image",
|
|
8058
|
-
"video"
|
|
8059
|
-
]);
|
|
8060
|
-
const MEDIA_FILENAME_REGEX = /^[a-f0-9]{12}\.[a-z0-9]+$/i;
|
|
8061
|
-
function isValidMediaKey(type, filename) {
|
|
8062
|
-
return ALLOWED_MEDIA_TYPES.has(type) && MEDIA_FILENAME_REGEX.test(filename);
|
|
8063
|
-
}
|
|
8064
8445
|
/**
|
|
8065
8446
|
* Get storage stats
|
|
8066
8447
|
* Must be defined BEFORE wildcard routes
|
|
@@ -8091,13 +8472,13 @@ mediaRouter.get("/stats", async (_req, res) => {
|
|
|
8091
8472
|
* Path format: /info/audio/abc123.mp3
|
|
8092
8473
|
*/
|
|
8093
8474
|
mediaRouter.get("/info/:type/:filename", async (req, res) => {
|
|
8475
|
+
const paramsResult = MediaSchemas.Params.safeParse(req.params);
|
|
8476
|
+
if (!paramsResult.success) {
|
|
8477
|
+
res.status(400).json({ error: z.prettifyError(paramsResult.error) });
|
|
8478
|
+
return;
|
|
8479
|
+
}
|
|
8094
8480
|
try {
|
|
8095
|
-
const type =
|
|
8096
|
-
const filename = req.params.filename;
|
|
8097
|
-
if (!isValidMediaKey(type, filename)) {
|
|
8098
|
-
res.status(400).json({ error: "Invalid media key" });
|
|
8099
|
-
return;
|
|
8100
|
-
}
|
|
8481
|
+
const { type, filename } = paramsResult.data;
|
|
8101
8482
|
const key = `${type}/${filename}`;
|
|
8102
8483
|
if (!await mediaExists(key)) {
|
|
8103
8484
|
res.status(404).json({ error: "Media not found" });
|
|
@@ -8125,13 +8506,13 @@ mediaRouter.get("/info/:type/:filename", async (req, res) => {
|
|
|
8125
8506
|
* The key is constructed from type + filename, e.g., "audio/abc123.mp3"
|
|
8126
8507
|
*/
|
|
8127
8508
|
mediaRouter.get("/:type/:filename", async (req, res) => {
|
|
8509
|
+
const paramsResult = MediaSchemas.Params.safeParse(req.params);
|
|
8510
|
+
if (!paramsResult.success) {
|
|
8511
|
+
res.status(400).json({ error: z.prettifyError(paramsResult.error) });
|
|
8512
|
+
return;
|
|
8513
|
+
}
|
|
8128
8514
|
try {
|
|
8129
|
-
const type =
|
|
8130
|
-
const filename = req.params.filename;
|
|
8131
|
-
if (!isValidMediaKey(type, filename)) {
|
|
8132
|
-
res.status(400).json({ error: "Invalid media key" });
|
|
8133
|
-
return;
|
|
8134
|
-
}
|
|
8515
|
+
const { type, filename } = paramsResult.data;
|
|
8135
8516
|
const key = `${type}/${filename}`;
|
|
8136
8517
|
logger_default.debug(`[Media API] Serving media: ${key}`);
|
|
8137
8518
|
if (!await mediaExists(key)) {
|
|
@@ -8986,61 +9367,183 @@ function modelScanCommand(program) {
|
|
|
8986
9367
|
});
|
|
8987
9368
|
}
|
|
8988
9369
|
|
|
9370
|
+
//#endregion
|
|
9371
|
+
//#region src/types/api/modelAudit.ts
|
|
9372
|
+
const CheckInstalledResponseSchema = z.object({
|
|
9373
|
+
installed: z.boolean(),
|
|
9374
|
+
version: z.string().nullable(),
|
|
9375
|
+
cwd: z.string()
|
|
9376
|
+
});
|
|
9377
|
+
const CheckPathRequestSchema = z.object({ path: z.string().trim().min(1, "No path provided") });
|
|
9378
|
+
const CheckPathResponseSchema = z.union([z.object({
|
|
9379
|
+
exists: z.literal(false),
|
|
9380
|
+
type: z.null()
|
|
9381
|
+
}), z.object({
|
|
9382
|
+
exists: z.literal(true),
|
|
9383
|
+
type: z.enum(["directory", "file"]),
|
|
9384
|
+
absolutePath: z.string(),
|
|
9385
|
+
name: z.string()
|
|
9386
|
+
})]);
|
|
9387
|
+
const ScanRequestSchema = z.object({
|
|
9388
|
+
paths: z.array(z.string()).min(1, "No paths provided").refine((arr) => arr.some((p) => p.trim() !== ""), { message: "No valid paths to scan" }),
|
|
9389
|
+
options: z.object({
|
|
9390
|
+
blacklist: z.array(z.string()).optional(),
|
|
9391
|
+
timeout: z.number().positive().optional(),
|
|
9392
|
+
maxFileSize: z.string().optional(),
|
|
9393
|
+
maxTotalSize: z.string().optional(),
|
|
9394
|
+
verbose: z.boolean().optional(),
|
|
9395
|
+
format: z.enum([
|
|
9396
|
+
"text",
|
|
9397
|
+
"json",
|
|
9398
|
+
"sarif"
|
|
9399
|
+
]).optional(),
|
|
9400
|
+
strict: z.boolean().optional(),
|
|
9401
|
+
dryRun: z.boolean().optional(),
|
|
9402
|
+
cache: z.boolean().optional(),
|
|
9403
|
+
quiet: z.boolean().optional(),
|
|
9404
|
+
progress: z.boolean().optional(),
|
|
9405
|
+
sbom: z.string().optional(),
|
|
9406
|
+
output: z.string().optional(),
|
|
9407
|
+
maxSize: z.string().optional(),
|
|
9408
|
+
persist: z.boolean().optional(),
|
|
9409
|
+
name: z.string().optional(),
|
|
9410
|
+
author: z.string().optional()
|
|
9411
|
+
}).optional().default({})
|
|
9412
|
+
});
|
|
9413
|
+
const ListScansQuerySchema = z.object({
|
|
9414
|
+
limit: z.coerce.number().int().min(1).max(100).optional().default(100),
|
|
9415
|
+
offset: z.coerce.number().int().min(0).optional().default(0),
|
|
9416
|
+
sort: z.enum([
|
|
9417
|
+
"createdAt",
|
|
9418
|
+
"name",
|
|
9419
|
+
"modelPath"
|
|
9420
|
+
]).optional().default("createdAt"),
|
|
9421
|
+
order: z.enum(["asc", "desc"]).optional().default("desc"),
|
|
9422
|
+
search: z.string().optional()
|
|
9423
|
+
});
|
|
9424
|
+
/** Shape returned by ModelAudit.toJSON(). */
|
|
9425
|
+
const ModelAuditRecordSchema = z.object({
|
|
9426
|
+
id: z.string(),
|
|
9427
|
+
createdAt: TimestampSchema,
|
|
9428
|
+
updatedAt: TimestampSchema,
|
|
9429
|
+
name: z.string().nullable().optional(),
|
|
9430
|
+
author: z.string().nullable().optional(),
|
|
9431
|
+
modelPath: z.string(),
|
|
9432
|
+
modelType: z.string().nullable().optional(),
|
|
9433
|
+
results: z.unknown(),
|
|
9434
|
+
checks: z.unknown().nullable().optional(),
|
|
9435
|
+
issues: z.unknown().nullable().optional(),
|
|
9436
|
+
hasErrors: z.boolean(),
|
|
9437
|
+
totalChecks: z.number().nullable().optional(),
|
|
9438
|
+
passedChecks: z.number().nullable().optional(),
|
|
9439
|
+
failedChecks: z.number().nullable().optional(),
|
|
9440
|
+
metadata: z.unknown().nullable().optional()
|
|
9441
|
+
}).passthrough();
|
|
9442
|
+
const ListScansResponseSchema = z.object({
|
|
9443
|
+
scans: z.array(ModelAuditRecordSchema),
|
|
9444
|
+
total: z.number(),
|
|
9445
|
+
limit: z.number(),
|
|
9446
|
+
offset: z.number()
|
|
9447
|
+
});
|
|
9448
|
+
const GetLatestScanResponseSchema = ModelAuditRecordSchema;
|
|
9449
|
+
const GetScanParamsSchema = z.object({ id: z.string().min(1) });
|
|
9450
|
+
const GetScanResponseSchema = ModelAuditRecordSchema;
|
|
9451
|
+
const DeleteScanParamsSchema = z.object({ id: z.string().min(1) });
|
|
9452
|
+
const DeleteScanResponseSchema = z.object({
|
|
9453
|
+
success: z.literal(true),
|
|
9454
|
+
message: z.string()
|
|
9455
|
+
});
|
|
9456
|
+
const ModelAuditSchemas = {
|
|
9457
|
+
CheckInstalled: { Response: CheckInstalledResponseSchema },
|
|
9458
|
+
CheckPath: {
|
|
9459
|
+
Request: CheckPathRequestSchema,
|
|
9460
|
+
Response: CheckPathResponseSchema
|
|
9461
|
+
},
|
|
9462
|
+
Scan: { Request: ScanRequestSchema },
|
|
9463
|
+
ListScans: {
|
|
9464
|
+
Query: ListScansQuerySchema,
|
|
9465
|
+
Response: ListScansResponseSchema
|
|
9466
|
+
},
|
|
9467
|
+
GetLatestScan: { Response: GetLatestScanResponseSchema },
|
|
9468
|
+
GetScan: {
|
|
9469
|
+
Params: GetScanParamsSchema,
|
|
9470
|
+
Response: GetScanResponseSchema
|
|
9471
|
+
},
|
|
9472
|
+
DeleteScan: {
|
|
9473
|
+
Params: DeleteScanParamsSchema,
|
|
9474
|
+
Response: DeleteScanResponseSchema
|
|
9475
|
+
}
|
|
9476
|
+
};
|
|
9477
|
+
|
|
9478
|
+
//#endregion
|
|
9479
|
+
//#region src/server/utils/errors.ts
|
|
9480
|
+
/**
|
|
9481
|
+
* Send a standardized error response.
|
|
9482
|
+
*
|
|
9483
|
+
* All error responses use the shape `{ error: string }`.
|
|
9484
|
+
* Internal details are logged but never exposed to the client.
|
|
9485
|
+
*/
|
|
9486
|
+
function sendError(res, status, publicMessage, internalError) {
|
|
9487
|
+
if (internalError) logger_default.error(publicMessage, { error: internalError });
|
|
9488
|
+
res.status(status).json({ error: publicMessage });
|
|
9489
|
+
}
|
|
9490
|
+
|
|
8989
9491
|
//#endregion
|
|
8990
9492
|
//#region src/server/routes/modelAudit.ts
|
|
8991
9493
|
const modelAuditRouter = Router();
|
|
8992
9494
|
modelAuditRouter.get("/check-installed", async (_req, res) => {
|
|
8993
9495
|
try {
|
|
8994
9496
|
const { installed, version } = await checkModelAuditInstalled();
|
|
8995
|
-
res.json({
|
|
9497
|
+
res.json(ModelAuditSchemas.CheckInstalled.Response.parse({
|
|
8996
9498
|
installed,
|
|
8997
9499
|
version,
|
|
8998
9500
|
cwd: process.cwd()
|
|
8999
|
-
});
|
|
9501
|
+
}));
|
|
9000
9502
|
} catch {
|
|
9001
|
-
res.json({
|
|
9503
|
+
res.json(ModelAuditSchemas.CheckInstalled.Response.parse({
|
|
9002
9504
|
installed: false,
|
|
9003
9505
|
version: null,
|
|
9004
9506
|
cwd: process.cwd()
|
|
9005
|
-
});
|
|
9507
|
+
}));
|
|
9006
9508
|
}
|
|
9007
9509
|
});
|
|
9008
9510
|
modelAuditRouter.post("/check-path", async (req, res) => {
|
|
9511
|
+
const bodyResult = ModelAuditSchemas.CheckPath.Request.safeParse(req.body);
|
|
9512
|
+
if (!bodyResult.success) {
|
|
9513
|
+
res.status(400).json({ error: z.prettifyError(bodyResult.error) });
|
|
9514
|
+
return;
|
|
9515
|
+
}
|
|
9009
9516
|
try {
|
|
9010
|
-
const { path: inputPath } =
|
|
9011
|
-
if (!inputPath) {
|
|
9012
|
-
res.status(400).json({ error: "No path provided" });
|
|
9013
|
-
return;
|
|
9014
|
-
}
|
|
9517
|
+
const { path: inputPath } = bodyResult.data;
|
|
9015
9518
|
let expandedPath = inputPath;
|
|
9016
9519
|
if (expandedPath.startsWith("~/")) expandedPath = path$1.join(os.homedir(), expandedPath.slice(2));
|
|
9017
9520
|
const absolutePath = path$1.isAbsolute(expandedPath) ? expandedPath : path$1.resolve(process.cwd(), expandedPath);
|
|
9018
9521
|
if (!fs$1.existsSync(absolutePath)) {
|
|
9019
|
-
res.json({
|
|
9522
|
+
res.json(ModelAuditSchemas.CheckPath.Response.parse({
|
|
9020
9523
|
exists: false,
|
|
9021
9524
|
type: null
|
|
9022
|
-
});
|
|
9525
|
+
}));
|
|
9023
9526
|
return;
|
|
9024
9527
|
}
|
|
9025
9528
|
const type = fs$1.statSync(absolutePath).isDirectory() ? "directory" : "file";
|
|
9026
|
-
res.json({
|
|
9529
|
+
res.json(ModelAuditSchemas.CheckPath.Response.parse({
|
|
9027
9530
|
exists: true,
|
|
9028
9531
|
type,
|
|
9029
9532
|
absolutePath,
|
|
9030
9533
|
name: path$1.basename(absolutePath)
|
|
9031
|
-
});
|
|
9534
|
+
}));
|
|
9032
9535
|
} catch (error) {
|
|
9033
|
-
|
|
9034
|
-
res.status(500).json({ error: String(error) });
|
|
9536
|
+
sendError(res, 500, "Failed to check path", error);
|
|
9035
9537
|
}
|
|
9036
9538
|
});
|
|
9037
9539
|
modelAuditRouter.post("/scan", async (req, res) => {
|
|
9540
|
+
const bodyResult = ModelAuditSchemas.Scan.Request.safeParse(req.body);
|
|
9541
|
+
if (!bodyResult.success) {
|
|
9542
|
+
res.status(400).json({ error: z.prettifyError(bodyResult.error) });
|
|
9543
|
+
return;
|
|
9544
|
+
}
|
|
9038
9545
|
try {
|
|
9039
|
-
const { paths, options
|
|
9040
|
-
if (!paths || !Array.isArray(paths) || paths.length === 0) {
|
|
9041
|
-
res.status(400).json({ error: "No paths provided" });
|
|
9042
|
-
return;
|
|
9043
|
-
}
|
|
9546
|
+
const { paths, options } = bodyResult.data;
|
|
9044
9547
|
const { installed } = await checkModelAuditInstalled();
|
|
9045
9548
|
if (!installed) {
|
|
9046
9549
|
res.status(400).json({ error: "ModelAudit is not installed. Please install it using: pip install modelaudit" });
|
|
@@ -9073,9 +9576,9 @@ modelAuditRouter.post("/scan", async (req, res) => {
|
|
|
9073
9576
|
telemetry_default.record("webui_api", {
|
|
9074
9577
|
event: "model_scan",
|
|
9075
9578
|
pathCount: paths.length,
|
|
9076
|
-
hasBlacklist: options.blacklist?.length > 0,
|
|
9077
|
-
timeout: options.timeout,
|
|
9078
|
-
verbose: options.verbose,
|
|
9579
|
+
hasBlacklist: (options.blacklist?.length ?? 0) > 0,
|
|
9580
|
+
timeout: options.timeout ?? 0,
|
|
9581
|
+
verbose: options.verbose ?? false,
|
|
9079
9582
|
persist
|
|
9080
9583
|
});
|
|
9081
9584
|
const modelAudit = spawn("modelaudit", args);
|
|
@@ -9113,16 +9616,15 @@ modelAuditRouter.post("/scan", async (req, res) => {
|
|
|
9113
9616
|
errorMessage = "Permission denied when trying to execute modelaudit";
|
|
9114
9617
|
suggestion = "Check that modelaudit is executable and you have the necessary permissions";
|
|
9115
9618
|
}
|
|
9619
|
+
logger_default.error("Failed to start modelaudit", {
|
|
9620
|
+
error: error.message,
|
|
9621
|
+
command: "modelaudit",
|
|
9622
|
+
args,
|
|
9623
|
+
paths: resolvedPaths
|
|
9624
|
+
});
|
|
9116
9625
|
safeRespond(500, {
|
|
9117
9626
|
error: errorMessage,
|
|
9118
|
-
|
|
9119
|
-
suggestion,
|
|
9120
|
-
debug: {
|
|
9121
|
-
command: "modelaudit",
|
|
9122
|
-
args,
|
|
9123
|
-
paths: resolvedPaths,
|
|
9124
|
-
cwd: process.cwd()
|
|
9125
|
-
}
|
|
9627
|
+
suggestion
|
|
9126
9628
|
});
|
|
9127
9629
|
});
|
|
9128
9630
|
modelAudit.on("close", async (code) => {
|
|
@@ -9201,35 +9703,32 @@ modelAuditRouter.post("/scan", async (req, res) => {
|
|
|
9201
9703
|
};
|
|
9202
9704
|
}
|
|
9203
9705
|
}
|
|
9204
|
-
|
|
9205
|
-
error: errorMessage,
|
|
9706
|
+
logger_default.error("Model scan failed", {
|
|
9206
9707
|
exitCode: code,
|
|
9207
9708
|
stderr: stderr || void 0,
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9212
|
-
|
|
9213
|
-
|
|
9214
|
-
|
|
9215
|
-
}
|
|
9709
|
+
command: "modelaudit",
|
|
9710
|
+
args,
|
|
9711
|
+
paths: resolvedPaths
|
|
9712
|
+
});
|
|
9713
|
+
safeRespond(500, {
|
|
9714
|
+
error: errorMessage,
|
|
9715
|
+
...errorDetails
|
|
9216
9716
|
});
|
|
9217
9717
|
return;
|
|
9218
9718
|
}
|
|
9219
9719
|
try {
|
|
9220
9720
|
const jsonOutput = stdout.trim();
|
|
9221
9721
|
if (!jsonOutput) {
|
|
9722
|
+
logger_default.error("No output from model scan", {
|
|
9723
|
+
stderr: stderr || void 0,
|
|
9724
|
+
command: "modelaudit",
|
|
9725
|
+
args,
|
|
9726
|
+
paths: resolvedPaths,
|
|
9727
|
+
exitCode: code
|
|
9728
|
+
});
|
|
9222
9729
|
safeRespond(500, {
|
|
9223
9730
|
error: "No output received from model scan",
|
|
9224
|
-
|
|
9225
|
-
suggestion: "The scan may have failed silently. Check that the model files are valid and accessible.",
|
|
9226
|
-
debug: {
|
|
9227
|
-
command: "modelaudit",
|
|
9228
|
-
args,
|
|
9229
|
-
paths: resolvedPaths,
|
|
9230
|
-
cwd: process.cwd(),
|
|
9231
|
-
exitCode: code
|
|
9232
|
-
}
|
|
9731
|
+
suggestion: "The scan may have failed silently. Check that the model files are valid and accessible."
|
|
9233
9732
|
});
|
|
9234
9733
|
return;
|
|
9235
9734
|
}
|
|
@@ -9237,20 +9736,18 @@ modelAuditRouter.post("/scan", async (req, res) => {
|
|
|
9237
9736
|
try {
|
|
9238
9737
|
scanResults = JSON.parse(jsonOutput);
|
|
9239
9738
|
} catch (parseError) {
|
|
9240
|
-
logger_default.error(
|
|
9241
|
-
|
|
9242
|
-
error: "Failed to parse scan results - invalid JSON output",
|
|
9243
|
-
parseError: String(parseError),
|
|
9739
|
+
logger_default.error("Failed to parse model scan output", {
|
|
9740
|
+
parseError,
|
|
9244
9741
|
output: jsonOutput.substring(0, 1e3),
|
|
9245
9742
|
stderr: stderr || void 0,
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
-
|
|
9252
|
-
|
|
9253
|
-
|
|
9743
|
+
command: "modelaudit",
|
|
9744
|
+
args,
|
|
9745
|
+
paths: resolvedPaths,
|
|
9746
|
+
exitCode: code
|
|
9747
|
+
});
|
|
9748
|
+
safeRespond(500, {
|
|
9749
|
+
error: "Failed to parse scan results - invalid JSON output",
|
|
9750
|
+
suggestion: "The model scan may have produced invalid output. Check the raw output for error messages."
|
|
9254
9751
|
});
|
|
9255
9752
|
return;
|
|
9256
9753
|
}
|
|
@@ -9258,7 +9755,7 @@ modelAuditRouter.post("/scan", async (req, res) => {
|
|
|
9258
9755
|
if (persist) try {
|
|
9259
9756
|
auditId = (await ModelAudit.create({
|
|
9260
9757
|
name: options.name || `API scan ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
9261
|
-
author: options.author
|
|
9758
|
+
author: options.author ?? void 0,
|
|
9262
9759
|
modelPath: resolvedPaths.join(", "),
|
|
9263
9760
|
results: {
|
|
9264
9761
|
...scanResults,
|
|
@@ -9287,44 +9784,32 @@ modelAuditRouter.post("/scan", async (req, res) => {
|
|
|
9287
9784
|
persisted: persist && !!auditId
|
|
9288
9785
|
});
|
|
9289
9786
|
} catch (error) {
|
|
9290
|
-
logger_default.error(
|
|
9291
|
-
safeRespond(500, {
|
|
9292
|
-
error: "Error processing scan results",
|
|
9293
|
-
details: String(error)
|
|
9294
|
-
});
|
|
9787
|
+
logger_default.error("Error processing model scan results", { error });
|
|
9788
|
+
safeRespond(500, { error: "Error processing scan results" });
|
|
9295
9789
|
}
|
|
9296
9790
|
});
|
|
9297
9791
|
} catch (error) {
|
|
9298
|
-
|
|
9299
|
-
res.status(500).json({ error: String(error) });
|
|
9792
|
+
sendError(res, 500, "Failed to start model scan", error);
|
|
9300
9793
|
}
|
|
9301
9794
|
});
|
|
9302
|
-
const VALID_SORT_FIELDS = [
|
|
9303
|
-
"createdAt",
|
|
9304
|
-
"name",
|
|
9305
|
-
"modelPath"
|
|
9306
|
-
];
|
|
9307
|
-
const VALID_SORT_ORDERS = ["asc", "desc"];
|
|
9308
9795
|
modelAuditRouter.get("/scans", async (req, res) => {
|
|
9796
|
+
const queryResult = ModelAuditSchemas.ListScans.Query.safeParse(req.query);
|
|
9797
|
+
if (!queryResult.success) {
|
|
9798
|
+
res.status(400).json({ error: z.prettifyError(queryResult.error) });
|
|
9799
|
+
return;
|
|
9800
|
+
}
|
|
9309
9801
|
try {
|
|
9310
|
-
const limit
|
|
9311
|
-
const offset = Math.max(0, parseInt(req.query.offset) || 0);
|
|
9312
|
-
const sortParam = req.query.sort || "createdAt";
|
|
9313
|
-
const orderParam = req.query.order || "desc";
|
|
9314
|
-
const search = req.query.search;
|
|
9315
|
-
const sort = VALID_SORT_FIELDS.includes(sortParam) ? sortParam : "createdAt";
|
|
9316
|
-
const order = VALID_SORT_ORDERS.includes(orderParam) ? orderParam : "desc";
|
|
9802
|
+
const { limit, offset, sort, order, search } = queryResult.data;
|
|
9317
9803
|
const audits = await ModelAudit.getMany(limit, offset, sort, order, search);
|
|
9318
9804
|
const total = await ModelAudit.count(search);
|
|
9319
|
-
res.json({
|
|
9805
|
+
res.json(ModelAuditSchemas.ListScans.Response.parse({
|
|
9320
9806
|
scans: audits.map((audit) => audit.toJSON()),
|
|
9321
9807
|
total,
|
|
9322
9808
|
limit,
|
|
9323
9809
|
offset
|
|
9324
|
-
});
|
|
9810
|
+
}));
|
|
9325
9811
|
} catch (error) {
|
|
9326
|
-
|
|
9327
|
-
res.status(500).json({ error: String(error) });
|
|
9812
|
+
sendError(res, 500, "Failed to fetch model scans", error);
|
|
9328
9813
|
}
|
|
9329
9814
|
});
|
|
9330
9815
|
modelAuditRouter.get("/scans/latest", async (_req, res) => {
|
|
@@ -9334,40 +9819,47 @@ modelAuditRouter.get("/scans/latest", async (_req, res) => {
|
|
|
9334
9819
|
res.status(404).json({ error: "No scans found" });
|
|
9335
9820
|
return;
|
|
9336
9821
|
}
|
|
9337
|
-
res.json(audits[0].toJSON());
|
|
9822
|
+
res.json(ModelAuditSchemas.GetLatestScan.Response.parse(audits[0].toJSON()));
|
|
9338
9823
|
} catch (error) {
|
|
9339
|
-
|
|
9340
|
-
res.status(500).json({ error: String(error) });
|
|
9824
|
+
sendError(res, 500, "Failed to fetch latest model scan", error);
|
|
9341
9825
|
}
|
|
9342
9826
|
});
|
|
9343
9827
|
modelAuditRouter.get("/scans/:id", async (req, res) => {
|
|
9828
|
+
const paramsResult = ModelAuditSchemas.GetScan.Params.safeParse(req.params);
|
|
9829
|
+
if (!paramsResult.success) {
|
|
9830
|
+
res.status(400).json({ error: z.prettifyError(paramsResult.error) });
|
|
9831
|
+
return;
|
|
9832
|
+
}
|
|
9344
9833
|
try {
|
|
9345
|
-
const audit = await ModelAudit.findById(
|
|
9834
|
+
const audit = await ModelAudit.findById(paramsResult.data.id);
|
|
9346
9835
|
if (!audit) {
|
|
9347
9836
|
res.status(404).json({ error: "Model scan not found" });
|
|
9348
9837
|
return;
|
|
9349
9838
|
}
|
|
9350
|
-
res.json(audit.toJSON());
|
|
9839
|
+
res.json(ModelAuditSchemas.GetScan.Response.parse(audit.toJSON()));
|
|
9351
9840
|
} catch (error) {
|
|
9352
|
-
|
|
9353
|
-
res.status(500).json({ error: String(error) });
|
|
9841
|
+
sendError(res, 500, "Failed to fetch model scan", error);
|
|
9354
9842
|
}
|
|
9355
9843
|
});
|
|
9356
9844
|
modelAuditRouter.delete("/scans/:id", async (req, res) => {
|
|
9845
|
+
const paramsResult = ModelAuditSchemas.DeleteScan.Params.safeParse(req.params);
|
|
9846
|
+
if (!paramsResult.success) {
|
|
9847
|
+
res.status(400).json({ error: z.prettifyError(paramsResult.error) });
|
|
9848
|
+
return;
|
|
9849
|
+
}
|
|
9357
9850
|
try {
|
|
9358
|
-
const audit = await ModelAudit.findById(
|
|
9851
|
+
const audit = await ModelAudit.findById(paramsResult.data.id);
|
|
9359
9852
|
if (!audit) {
|
|
9360
9853
|
res.status(404).json({ error: "Model scan not found" });
|
|
9361
9854
|
return;
|
|
9362
9855
|
}
|
|
9363
9856
|
await audit.delete();
|
|
9364
|
-
res.json({
|
|
9857
|
+
res.json(ModelAuditSchemas.DeleteScan.Response.parse({
|
|
9365
9858
|
success: true,
|
|
9366
9859
|
message: "Model scan deleted successfully"
|
|
9367
|
-
});
|
|
9860
|
+
}));
|
|
9368
9861
|
} catch (error) {
|
|
9369
|
-
|
|
9370
|
-
res.status(500).json({ error: String(error) });
|
|
9862
|
+
sendError(res, 500, "Failed to delete model scan", error);
|
|
9371
9863
|
}
|
|
9372
9864
|
});
|
|
9373
9865
|
|
|
@@ -9761,6 +10253,16 @@ const defaultProviders = [].concat([
|
|
|
9761
10253
|
}
|
|
9762
10254
|
}
|
|
9763
10255
|
]).concat([
|
|
10256
|
+
{
|
|
10257
|
+
id: "vertex:gemini-3.1-pro-preview",
|
|
10258
|
+
label: "Vertex: Gemini 3.1 Pro",
|
|
10259
|
+
config: { generationConfig: {
|
|
10260
|
+
temperature: .5,
|
|
10261
|
+
maxOutputTokens: 1024,
|
|
10262
|
+
topP: .95,
|
|
10263
|
+
topK: 40
|
|
10264
|
+
} }
|
|
10265
|
+
},
|
|
9764
10266
|
{
|
|
9765
10267
|
id: "vertex:gemini-2.5-pro",
|
|
9766
10268
|
label: "Vertex: Gemini 2.5 Pro",
|
|
@@ -10242,10 +10744,11 @@ function discoverCommand(program, defaultConfig, defaultConfigPath) {
|
|
|
10242
10744
|
|
|
10243
10745
|
//#endregion
|
|
10244
10746
|
//#region src/types/api/providers.ts
|
|
10747
|
+
const ProviderOptionsWithIdSchema = ProviderOptionsSchema.extend({ id: z.string().min(1, "Provider ID is required") });
|
|
10245
10748
|
/** Request body for testing provider connectivity. */
|
|
10246
10749
|
const TestProviderRequestSchema = z.object({
|
|
10247
10750
|
prompt: z.string().optional(),
|
|
10248
|
-
providerOptions:
|
|
10751
|
+
providerOptions: ProviderOptionsWithIdSchema
|
|
10249
10752
|
});
|
|
10250
10753
|
/** Request body for testing request transforms. */
|
|
10251
10754
|
const TestRequestTransformSchema = z.object({
|
|
@@ -10257,11 +10760,28 @@ const TestResponseTransformSchema = z.object({
|
|
|
10257
10760
|
transformCode: z.string().optional(),
|
|
10258
10761
|
response: z.string()
|
|
10259
10762
|
});
|
|
10763
|
+
/** Request body for generating HTTP provider config from example request/response. */
|
|
10764
|
+
const HttpGeneratorRequestSchema = z.object({
|
|
10765
|
+
requestExample: z.string().min(1),
|
|
10766
|
+
responseExample: z.string().optional()
|
|
10767
|
+
});
|
|
10768
|
+
/** Request body for testing multi-turn session functionality. */
|
|
10769
|
+
const TestSessionRequestSchema = z.object({
|
|
10770
|
+
provider: ProviderOptionsWithIdSchema,
|
|
10771
|
+
sessionConfig: z.object({
|
|
10772
|
+
sessionSource: z.string().optional(),
|
|
10773
|
+
sessionParser: z.string().optional()
|
|
10774
|
+
}).optional(),
|
|
10775
|
+
mainInputVariable: z.string().optional()
|
|
10776
|
+
});
|
|
10260
10777
|
/** Grouped schemas for server-side validation. */
|
|
10261
10778
|
const ProviderSchemas = {
|
|
10262
10779
|
Test: { Request: TestProviderRequestSchema },
|
|
10780
|
+
Discover: { Request: ProviderOptionsWithIdSchema },
|
|
10781
|
+
HttpGenerator: { Request: HttpGeneratorRequestSchema },
|
|
10263
10782
|
TestRequestTransform: { Request: TestRequestTransformSchema },
|
|
10264
|
-
TestResponseTransform: { Request: TestResponseTransformSchema }
|
|
10783
|
+
TestResponseTransform: { Request: TestResponseTransformSchema },
|
|
10784
|
+
TestSession: { Request: TestSessionRequestSchema }
|
|
10265
10785
|
};
|
|
10266
10786
|
|
|
10267
10787
|
//#endregion
|
|
@@ -10963,15 +11483,12 @@ providersRouter.get("/config-status", (_req, res) => {
|
|
|
10963
11483
|
}
|
|
10964
11484
|
});
|
|
10965
11485
|
providersRouter.post("/test", async (req, res) => {
|
|
10966
|
-
|
|
10967
|
-
|
|
10968
|
-
|
|
10969
|
-
} catch (e) {
|
|
10970
|
-
res.status(400).json({ error: z.prettifyError(e) });
|
|
11486
|
+
const bodyResult = ProviderSchemas.Test.Request.safeParse(req.body);
|
|
11487
|
+
if (!bodyResult.success) {
|
|
11488
|
+
res.status(400).json({ error: z.prettifyError(bodyResult.error) });
|
|
10971
11489
|
return;
|
|
10972
11490
|
}
|
|
10973
|
-
const providerOptions =
|
|
10974
|
-
invariant(payload.providerOptions.id, "id is required");
|
|
11491
|
+
const { providerOptions } = bodyResult.data;
|
|
10975
11492
|
const result = await testProviderConnectivity({
|
|
10976
11493
|
provider: await loadApiProvider(providerOptions.id, { options: {
|
|
10977
11494
|
...providerOptions,
|
|
@@ -10980,7 +11497,7 @@ providersRouter.post("/test", async (req, res) => {
|
|
|
10980
11497
|
maxRetries: 1
|
|
10981
11498
|
}
|
|
10982
11499
|
} }),
|
|
10983
|
-
prompt:
|
|
11500
|
+
prompt: bodyResult.data.prompt,
|
|
10984
11501
|
inputs: providerOptions.inputs || providerOptions.config?.inputs
|
|
10985
11502
|
});
|
|
10986
11503
|
res.status(200).json({
|
|
@@ -10997,15 +11514,12 @@ providersRouter.post("/test", async (req, res) => {
|
|
|
10997
11514
|
});
|
|
10998
11515
|
});
|
|
10999
11516
|
providersRouter.post("/discover", async (req, res) => {
|
|
11000
|
-
const
|
|
11001
|
-
|
|
11002
|
-
|
|
11003
|
-
providerOptions = ProviderOptionsSchema.parse(body);
|
|
11004
|
-
} catch (e) {
|
|
11005
|
-
res.status(400).json({ error: z.prettifyError(e) });
|
|
11517
|
+
const bodyResult = ProviderSchemas.Discover.Request.safeParse(req.body);
|
|
11518
|
+
if (!bodyResult.success) {
|
|
11519
|
+
res.status(400).json({ error: z.prettifyError(bodyResult.error) });
|
|
11006
11520
|
return;
|
|
11007
11521
|
}
|
|
11008
|
-
|
|
11522
|
+
const providerOptions = bodyResult.data;
|
|
11009
11523
|
if (neverGenerateRemote()) {
|
|
11010
11524
|
res.status(400).json({ error: "Requires remote generation be enabled." });
|
|
11011
11525
|
return;
|
|
@@ -11015,21 +11529,21 @@ providersRouter.post("/discover", async (req, res) => {
|
|
|
11015
11529
|
if (result) res.json(result);
|
|
11016
11530
|
else res.status(500).json({ error: "Discovery failed to discover the target's purpose." });
|
|
11017
11531
|
} catch (e) {
|
|
11018
|
-
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
11019
11532
|
logger_default.error("Error calling target purpose discovery", {
|
|
11020
11533
|
error: e,
|
|
11021
11534
|
providerOptions
|
|
11022
11535
|
});
|
|
11023
|
-
res
|
|
11536
|
+
sendError(res, 500, "Discovery failed to discover the target's purpose");
|
|
11024
11537
|
return;
|
|
11025
11538
|
}
|
|
11026
11539
|
});
|
|
11027
11540
|
providersRouter.post("/http-generator", async (req, res) => {
|
|
11028
|
-
const
|
|
11029
|
-
if (!
|
|
11030
|
-
res.status(400).json({ error:
|
|
11541
|
+
const bodyResult = ProviderSchemas.HttpGenerator.Request.safeParse(req.body);
|
|
11542
|
+
if (!bodyResult.success) {
|
|
11543
|
+
res.status(400).json({ error: z.prettifyError(bodyResult.error) });
|
|
11031
11544
|
return;
|
|
11032
11545
|
}
|
|
11546
|
+
const { requestExample, responseExample } = bodyResult.data;
|
|
11033
11547
|
const HOST = getEnvString("PROMPTFOO_CLOUD_API_URL", "https://api.promptfoo.app");
|
|
11034
11548
|
try {
|
|
11035
11549
|
logger_default.debug("[POST /providers/http-generator] Calling HTTP provider generator API", {
|
|
@@ -11050,27 +11564,28 @@ providersRouter.post("/http-generator", async (req, res) => {
|
|
|
11050
11564
|
status: response.status,
|
|
11051
11565
|
errorText
|
|
11052
11566
|
});
|
|
11053
|
-
res.status(response.status).json({
|
|
11054
|
-
error: `HTTP error! status: ${response.status}`,
|
|
11055
|
-
details: errorText
|
|
11056
|
-
});
|
|
11567
|
+
res.status(response.status).json({ error: `HTTP error! status: ${response.status}` });
|
|
11057
11568
|
return;
|
|
11058
11569
|
}
|
|
11059
11570
|
const data = await response.json();
|
|
11060
11571
|
logger_default.debug("[POST /providers/http-generator] Successfully generated config");
|
|
11061
11572
|
res.status(200).json(data);
|
|
11062
11573
|
} catch (error) {
|
|
11063
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
11064
11574
|
logger_default.error("[POST /providers/http-generator] Error calling HTTP provider generator", { error });
|
|
11065
|
-
res
|
|
11066
|
-
error: "Failed to generate HTTP configuration",
|
|
11067
|
-
details: errorMessage
|
|
11068
|
-
});
|
|
11575
|
+
sendError(res, 500, "Failed to generate HTTP configuration");
|
|
11069
11576
|
}
|
|
11070
11577
|
});
|
|
11071
11578
|
providersRouter.post("/test-request-transform", async (req, res) => {
|
|
11579
|
+
const bodyResult = ProviderSchemas.TestRequestTransform.Request.safeParse(req.body);
|
|
11580
|
+
if (!bodyResult.success) {
|
|
11581
|
+
res.status(400).json({
|
|
11582
|
+
success: false,
|
|
11583
|
+
error: z.prettifyError(bodyResult.error)
|
|
11584
|
+
});
|
|
11585
|
+
return;
|
|
11586
|
+
}
|
|
11587
|
+
const { transformCode, prompt } = bodyResult.data;
|
|
11072
11588
|
try {
|
|
11073
|
-
const { transformCode, prompt } = ProviderSchemas.TestRequestTransform.Request.parse(req.body);
|
|
11074
11589
|
const result = await (await createTransformRequest(transformCode && transformCode.trim() ? transformCode : void 0))(prompt, {}, {
|
|
11075
11590
|
prompt: {
|
|
11076
11591
|
raw: prompt,
|
|
@@ -11090,13 +11605,6 @@ providersRouter.post("/test-request-transform", async (req, res) => {
|
|
|
11090
11605
|
result
|
|
11091
11606
|
});
|
|
11092
11607
|
} catch (error) {
|
|
11093
|
-
if (error instanceof z.ZodError) {
|
|
11094
|
-
res.status(400).json({
|
|
11095
|
-
success: false,
|
|
11096
|
-
error: z.prettifyError(error)
|
|
11097
|
-
});
|
|
11098
|
-
return;
|
|
11099
|
-
}
|
|
11100
11608
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
11101
11609
|
logger_default.error("[POST /providers/test-request-transform] Error", { error });
|
|
11102
11610
|
res.status(200).json({
|
|
@@ -11106,8 +11614,16 @@ providersRouter.post("/test-request-transform", async (req, res) => {
|
|
|
11106
11614
|
}
|
|
11107
11615
|
});
|
|
11108
11616
|
providersRouter.post("/test-response-transform", async (req, res) => {
|
|
11617
|
+
const bodyResult = ProviderSchemas.TestResponseTransform.Request.safeParse(req.body);
|
|
11618
|
+
if (!bodyResult.success) {
|
|
11619
|
+
res.status(400).json({
|
|
11620
|
+
success: false,
|
|
11621
|
+
error: z.prettifyError(bodyResult.error)
|
|
11622
|
+
});
|
|
11623
|
+
return;
|
|
11624
|
+
}
|
|
11625
|
+
const { transformCode, response: responseText } = bodyResult.data;
|
|
11109
11626
|
try {
|
|
11110
|
-
const { transformCode, response: responseText } = ProviderSchemas.TestResponseTransform.Request.parse(req.body);
|
|
11111
11627
|
const normalizedTransformCode = transformCode && transformCode.trim() ? transformCode : void 0;
|
|
11112
11628
|
let jsonData;
|
|
11113
11629
|
try {
|
|
@@ -11130,13 +11646,6 @@ providersRouter.post("/test-response-transform", async (req, res) => {
|
|
|
11130
11646
|
result: output
|
|
11131
11647
|
});
|
|
11132
11648
|
} catch (error) {
|
|
11133
|
-
if (error instanceof z.ZodError) {
|
|
11134
|
-
res.status(400).json({
|
|
11135
|
-
success: false,
|
|
11136
|
-
error: z.prettifyError(error)
|
|
11137
|
-
});
|
|
11138
|
-
return;
|
|
11139
|
-
}
|
|
11140
11649
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
11141
11650
|
logger_default.error("[POST /providers/test-response-transform] Error", { error });
|
|
11142
11651
|
res.status(200).json({
|
|
@@ -11146,10 +11655,13 @@ providersRouter.post("/test-response-transform", async (req, res) => {
|
|
|
11146
11655
|
}
|
|
11147
11656
|
});
|
|
11148
11657
|
providersRouter.post("/test-session", async (req, res) => {
|
|
11149
|
-
const
|
|
11658
|
+
const bodyResult = ProviderSchemas.TestSession.Request.safeParse(req.body);
|
|
11659
|
+
if (!bodyResult.success) {
|
|
11660
|
+
res.status(400).json({ error: z.prettifyError(bodyResult.error) });
|
|
11661
|
+
return;
|
|
11662
|
+
}
|
|
11663
|
+
const { provider: validatedProvider, sessionConfig, mainInputVariable } = bodyResult.data;
|
|
11150
11664
|
try {
|
|
11151
|
-
const validatedProvider = ProviderOptionsSchema.parse(providerOptions);
|
|
11152
|
-
invariant(validatedProvider.id, "Provider ID is required");
|
|
11153
11665
|
const result = await testProviderSession({
|
|
11154
11666
|
provider: await loadApiProvider(validatedProvider.id, { options: {
|
|
11155
11667
|
...validatedProvider,
|
|
@@ -11166,15 +11678,53 @@ providersRouter.post("/test-session", async (req, res) => {
|
|
|
11166
11678
|
});
|
|
11167
11679
|
res.json(result);
|
|
11168
11680
|
} catch (error) {
|
|
11169
|
-
|
|
11681
|
+
logger_default.error("[POST /providers/test-session] Error testing session", { error });
|
|
11170
11682
|
res.status(500).json({
|
|
11171
11683
|
success: false,
|
|
11172
|
-
message:
|
|
11173
|
-
error:
|
|
11684
|
+
message: "Failed to test session",
|
|
11685
|
+
error: "Failed to test session"
|
|
11174
11686
|
});
|
|
11175
11687
|
}
|
|
11176
11688
|
});
|
|
11177
11689
|
|
|
11690
|
+
//#endregion
|
|
11691
|
+
//#region src/types/api/redteam.ts
|
|
11692
|
+
const TestCaseGenerationSchema = z.object({
|
|
11693
|
+
plugin: z.object({
|
|
11694
|
+
id: z.string().refine((val) => ALL_PLUGINS.includes(val), { message: `Invalid plugin ID. Must be one of: ${ALL_PLUGINS.join(", ")}` }),
|
|
11695
|
+
config: PluginConfigSchema.optional().prefault({})
|
|
11696
|
+
}),
|
|
11697
|
+
strategy: z.object({
|
|
11698
|
+
id: z.string().refine((val) => ALL_STRATEGIES.includes(val), { message: `Invalid strategy ID. Must be one of: ${ALL_STRATEGIES.join(", ")}` }),
|
|
11699
|
+
config: StrategyConfigSchema.optional().prefault({})
|
|
11700
|
+
}),
|
|
11701
|
+
config: z.object({ applicationDefinition: z.object({ purpose: z.string().nullable() }) }),
|
|
11702
|
+
turn: z.int().min(0).optional().prefault(0),
|
|
11703
|
+
maxTurns: z.int().min(1).optional(),
|
|
11704
|
+
history: z.array(ConversationMessageSchema).optional().prefault([]),
|
|
11705
|
+
goal: z.string().optional(),
|
|
11706
|
+
stateful: z.boolean().optional(),
|
|
11707
|
+
count: z.int().min(1).max(10).optional().prefault(1)
|
|
11708
|
+
});
|
|
11709
|
+
const RedteamRunRequestSchema = z.object({
|
|
11710
|
+
config: z.record(z.string(), z.unknown()),
|
|
11711
|
+
force: z.boolean().optional(),
|
|
11712
|
+
verbose: z.boolean().optional(),
|
|
11713
|
+
delay: z.coerce.number().min(0).optional(),
|
|
11714
|
+
maxConcurrency: z.coerce.number().int().min(1).optional()
|
|
11715
|
+
});
|
|
11716
|
+
const RedteamTaskParamsSchema = z.object({ taskId: z.string().min(1).max(128) });
|
|
11717
|
+
const RedteamTaskRequestSchema = z.record(z.string(), z.unknown());
|
|
11718
|
+
/** Grouped schemas for server-side validation. */
|
|
11719
|
+
const RedteamSchemas = {
|
|
11720
|
+
GenerateTest: { Request: TestCaseGenerationSchema },
|
|
11721
|
+
Run: { Request: RedteamRunRequestSchema },
|
|
11722
|
+
Task: {
|
|
11723
|
+
Params: RedteamTaskParamsSchema,
|
|
11724
|
+
Request: RedteamTaskRequestSchema
|
|
11725
|
+
}
|
|
11726
|
+
};
|
|
11727
|
+
|
|
11178
11728
|
//#endregion
|
|
11179
11729
|
//#region src/server/services/redteamTestCaseGenerationService.ts
|
|
11180
11730
|
const MULTI_TURN_EMAIL = "anonymous@promptfoo.dev";
|
|
@@ -11471,34 +12021,14 @@ async function handleCrescendoLikeStrategy(ctx) {
|
|
|
11471
12021
|
//#endregion
|
|
11472
12022
|
//#region src/server/routes/redteam.ts
|
|
11473
12023
|
const redteamRouter = Router();
|
|
11474
|
-
const TestCaseGenerationSchema = z.object({
|
|
11475
|
-
plugin: z.object({
|
|
11476
|
-
id: z.string().refine((val) => ALL_PLUGINS.includes(val), { message: `Invalid plugin ID. Must be one of: ${ALL_PLUGINS.join(", ")}` }),
|
|
11477
|
-
config: PluginConfigSchema.optional().prefault({})
|
|
11478
|
-
}),
|
|
11479
|
-
strategy: z.object({
|
|
11480
|
-
id: z.string().refine((val) => ALL_STRATEGIES.includes(val), { message: `Invalid strategy ID. Must be one of: ${ALL_STRATEGIES.join(", ")}` }),
|
|
11481
|
-
config: StrategyConfigSchema.optional().prefault({})
|
|
11482
|
-
}),
|
|
11483
|
-
config: z.object({ applicationDefinition: z.object({ purpose: z.string().nullable() }) }),
|
|
11484
|
-
turn: z.int().min(0).optional().prefault(0),
|
|
11485
|
-
maxTurns: z.int().min(1).optional(),
|
|
11486
|
-
history: z.array(ConversationMessageSchema).optional().prefault([]),
|
|
11487
|
-
goal: z.string().optional(),
|
|
11488
|
-
stateful: z.boolean().optional(),
|
|
11489
|
-
count: z.int().min(1).max(10).optional().prefault(1)
|
|
11490
|
-
});
|
|
11491
12024
|
/**
|
|
11492
12025
|
* Generates a test case for a given plugin/strategy combination.
|
|
11493
12026
|
*/
|
|
11494
12027
|
redteamRouter.post("/generate-test", async (req, res) => {
|
|
11495
12028
|
try {
|
|
11496
|
-
const parsedBody =
|
|
12029
|
+
const parsedBody = RedteamSchemas.GenerateTest.Request.safeParse(req.body);
|
|
11497
12030
|
if (!parsedBody.success) {
|
|
11498
|
-
res.status(400).json({
|
|
11499
|
-
error: "Invalid request body",
|
|
11500
|
-
details: parsedBody.error.message
|
|
11501
|
-
});
|
|
12031
|
+
res.status(400).json({ error: z.prettifyError(parsedBody.error) });
|
|
11502
12032
|
return;
|
|
11503
12033
|
}
|
|
11504
12034
|
const { plugin, strategy, config, turn, maxTurns, history, goal: goalOverride, stateful, count } = parsedBody.data;
|
|
@@ -11547,11 +12077,8 @@ redteamRouter.post("/generate-test", async (req, res) => {
|
|
|
11547
12077
|
const strategyTestCases = await Strategies.find((s) => s.id === strategy.id).action(testCases, injectVar, strategy.config || {}, strategy.id);
|
|
11548
12078
|
if (strategyTestCases && strategyTestCases.length > 0) finalTestCases = strategyTestCases;
|
|
11549
12079
|
} catch (error) {
|
|
11550
|
-
logger_default.error(`Error applying strategy ${strategy.id}
|
|
11551
|
-
res.status(500).json({
|
|
11552
|
-
error: `Failed to apply strategy ${strategy.id}`,
|
|
11553
|
-
details: error instanceof Error ? error.message : String(error)
|
|
11554
|
-
});
|
|
12080
|
+
logger_default.error(`Error applying strategy ${strategy.id}`, { error });
|
|
12081
|
+
res.status(500).json({ error: `Failed to apply strategy ${strategy.id}` });
|
|
11555
12082
|
return;
|
|
11556
12083
|
}
|
|
11557
12084
|
const context = `This test case targets the ${plugin.id} plugin with strategy ${strategy.id} and was generated based on your application context. If the test case is not relevant to your application, you can modify the application definition to improve relevance.`;
|
|
@@ -11592,10 +12119,7 @@ redteamRouter.post("/generate-test", async (req, res) => {
|
|
|
11592
12119
|
message: error instanceof Error ? error.message : String(error),
|
|
11593
12120
|
strategy: strategy.id
|
|
11594
12121
|
});
|
|
11595
|
-
res.status(500).json({
|
|
11596
|
-
error: "Failed to generate multi-turn prompt",
|
|
11597
|
-
details: error instanceof Error ? error.message : String(error)
|
|
11598
|
-
});
|
|
12122
|
+
res.status(500).json({ error: "Failed to generate multi-turn prompt" });
|
|
11599
12123
|
return;
|
|
11600
12124
|
}
|
|
11601
12125
|
}
|
|
@@ -11622,16 +12146,21 @@ redteamRouter.post("/generate-test", async (req, res) => {
|
|
|
11622
12146
|
metadata: baseMetadata
|
|
11623
12147
|
});
|
|
11624
12148
|
} catch (error) {
|
|
11625
|
-
logger_default.error(
|
|
11626
|
-
res.status(500).json({
|
|
11627
|
-
error: "Failed to generate test case",
|
|
11628
|
-
details: error instanceof Error ? error.message : String(error)
|
|
11629
|
-
});
|
|
12149
|
+
logger_default.error("Error generating test case", { error });
|
|
12150
|
+
res.status(500).json({ error: "Failed to generate test case" });
|
|
11630
12151
|
}
|
|
11631
12152
|
});
|
|
11632
12153
|
let currentJobId = null;
|
|
11633
12154
|
let currentAbortController = null;
|
|
11634
12155
|
redteamRouter.post("/run", async (req, res) => {
|
|
12156
|
+
const bodyResult = RedteamSchemas.Run.Request.safeParse(req.body);
|
|
12157
|
+
if (!bodyResult.success) {
|
|
12158
|
+
res.status(400).json({
|
|
12159
|
+
success: false,
|
|
12160
|
+
error: z.prettifyError(bodyResult.error)
|
|
12161
|
+
});
|
|
12162
|
+
return;
|
|
12163
|
+
}
|
|
11635
12164
|
if (currentJobId) {
|
|
11636
12165
|
if (currentAbortController) currentAbortController.abort();
|
|
11637
12166
|
const existingJob = evalJobs.get(currentJobId);
|
|
@@ -11640,7 +12169,7 @@ redteamRouter.post("/run", async (req, res) => {
|
|
|
11640
12169
|
existingJob.logs.push("Job cancelled - new job started");
|
|
11641
12170
|
}
|
|
11642
12171
|
}
|
|
11643
|
-
const { config, force, verbose, delay, maxConcurrency } =
|
|
12172
|
+
const { config, force, verbose, delay, maxConcurrency } = bodyResult.data;
|
|
11644
12173
|
const id = crypto.randomUUID();
|
|
11645
12174
|
currentJobId = id;
|
|
11646
12175
|
currentAbortController = new AbortController();
|
|
@@ -11653,13 +12182,12 @@ redteamRouter.post("/run", async (req, res) => {
|
|
|
11653
12182
|
logs: []
|
|
11654
12183
|
});
|
|
11655
12184
|
cliState_default.webUI = true;
|
|
11656
|
-
const normalizedMaxConcurrency = Math.max(1, Number(maxConcurrency || "1"));
|
|
11657
12185
|
doRedteamRun({
|
|
11658
12186
|
liveRedteamConfig: config,
|
|
11659
12187
|
force,
|
|
11660
12188
|
verbose,
|
|
11661
|
-
delay:
|
|
11662
|
-
maxConcurrency:
|
|
12189
|
+
delay: delay ?? 0,
|
|
12190
|
+
maxConcurrency: maxConcurrency ?? 1,
|
|
11663
12191
|
logCallback: (message) => {
|
|
11664
12192
|
if (currentJobId === id) {
|
|
11665
12193
|
const job = evalJobs.get(id);
|
|
@@ -11725,7 +12253,23 @@ redteamRouter.post("/cancel", async (_req, res) => {
|
|
|
11725
12253
|
* Cloud's task registry (See server/src/routes/task.ts).
|
|
11726
12254
|
*/
|
|
11727
12255
|
redteamRouter.post("/:taskId", async (req, res) => {
|
|
11728
|
-
const
|
|
12256
|
+
const paramsResult = RedteamSchemas.Task.Params.safeParse(req.params);
|
|
12257
|
+
if (!paramsResult.success) {
|
|
12258
|
+
res.status(400).json({
|
|
12259
|
+
success: false,
|
|
12260
|
+
error: z.prettifyError(paramsResult.error)
|
|
12261
|
+
});
|
|
12262
|
+
return;
|
|
12263
|
+
}
|
|
12264
|
+
const bodyResult = RedteamSchemas.Task.Request.safeParse(req.body);
|
|
12265
|
+
if (!bodyResult.success) {
|
|
12266
|
+
res.status(400).json({
|
|
12267
|
+
success: false,
|
|
12268
|
+
error: z.prettifyError(bodyResult.error)
|
|
12269
|
+
});
|
|
12270
|
+
return;
|
|
12271
|
+
}
|
|
12272
|
+
const { taskId } = paramsResult.data;
|
|
11729
12273
|
const cloudFunctionUrl = getRemoteGenerationUrl();
|
|
11730
12274
|
logger_default.debug(`Received ${taskId} task request: ${JSON.stringify({
|
|
11731
12275
|
method: req.method,
|
|
@@ -11738,8 +12282,8 @@ redteamRouter.post("/:taskId", async (req, res) => {
|
|
|
11738
12282
|
method: "POST",
|
|
11739
12283
|
headers: { "Content-Type": "application/json" },
|
|
11740
12284
|
body: JSON.stringify({
|
|
11741
|
-
|
|
11742
|
-
|
|
12285
|
+
...bodyResult.data,
|
|
12286
|
+
task: taskId
|
|
11743
12287
|
})
|
|
11744
12288
|
});
|
|
11745
12289
|
if (!response.ok) {
|
|
@@ -11761,12 +12305,27 @@ redteamRouter.get("/status", async (_req, res) => {
|
|
|
11761
12305
|
});
|
|
11762
12306
|
});
|
|
11763
12307
|
|
|
12308
|
+
//#endregion
|
|
12309
|
+
//#region src/types/api/traces.ts
|
|
12310
|
+
const GetTracesByEvalParamsSchema = z.object({ evaluationId: z.string().min(1) });
|
|
12311
|
+
const GetTraceParamsSchema = z.object({ traceId: z.string().min(1) });
|
|
12312
|
+
/** Grouped schemas for server-side validation. */
|
|
12313
|
+
const TracesSchemas = {
|
|
12314
|
+
GetByEval: { Params: GetTracesByEvalParamsSchema },
|
|
12315
|
+
Get: { Params: GetTraceParamsSchema }
|
|
12316
|
+
};
|
|
12317
|
+
|
|
11764
12318
|
//#endregion
|
|
11765
12319
|
//#region src/server/routes/traces.ts
|
|
11766
12320
|
const tracesRouter = Router();
|
|
11767
12321
|
tracesRouter.get("/evaluation/:evaluationId", async (req, res) => {
|
|
12322
|
+
const paramsResult = TracesSchemas.GetByEval.Params.safeParse(req.params);
|
|
12323
|
+
if (!paramsResult.success) {
|
|
12324
|
+
res.status(400).json({ error: z.prettifyError(paramsResult.error) });
|
|
12325
|
+
return;
|
|
12326
|
+
}
|
|
11768
12327
|
try {
|
|
11769
|
-
const evaluationId =
|
|
12328
|
+
const { evaluationId } = paramsResult.data;
|
|
11770
12329
|
logger_default.debug(`[TracesRoute] Fetching traces for evaluation ${evaluationId}`);
|
|
11771
12330
|
const traces = await getTraceStore().getTracesByEvaluation(evaluationId);
|
|
11772
12331
|
logger_default.debug(`[TracesRoute] Found ${traces.length} traces for evaluation ${evaluationId}`);
|
|
@@ -11777,8 +12336,13 @@ tracesRouter.get("/evaluation/:evaluationId", async (req, res) => {
|
|
|
11777
12336
|
}
|
|
11778
12337
|
});
|
|
11779
12338
|
tracesRouter.get("/:traceId", async (req, res) => {
|
|
12339
|
+
const paramsResult = TracesSchemas.Get.Params.safeParse(req.params);
|
|
12340
|
+
if (!paramsResult.success) {
|
|
12341
|
+
res.status(400).json({ error: z.prettifyError(paramsResult.error) });
|
|
12342
|
+
return;
|
|
12343
|
+
}
|
|
11780
12344
|
try {
|
|
11781
|
-
const traceId =
|
|
12345
|
+
const { traceId } = paramsResult.data;
|
|
11782
12346
|
logger_default.debug(`[TracesRoute] Fetching trace ${traceId}`);
|
|
11783
12347
|
const trace = await getTraceStore().getTrace(traceId);
|
|
11784
12348
|
if (!trace) {
|
|
@@ -11811,6 +12375,7 @@ const UpdateUserResponseSchema = z.object({
|
|
|
11811
12375
|
success: z.boolean(),
|
|
11812
12376
|
message: z.string()
|
|
11813
12377
|
});
|
|
12378
|
+
const GetEmailStatusQuerySchema = z.object({ validate: z.unknown().optional().transform((v) => v === "true") });
|
|
11814
12379
|
const GetEmailStatusResponseSchema = z.object({
|
|
11815
12380
|
hasEmail: z.boolean(),
|
|
11816
12381
|
email: EmailSchema.optional(),
|
|
@@ -11854,7 +12419,10 @@ const UserSchemas = {
|
|
|
11854
12419
|
Request: UpdateUserRequestSchema,
|
|
11855
12420
|
Response: UpdateUserResponseSchema
|
|
11856
12421
|
},
|
|
11857
|
-
EmailStatus: {
|
|
12422
|
+
EmailStatus: {
|
|
12423
|
+
Query: GetEmailStatusQuerySchema,
|
|
12424
|
+
Response: GetEmailStatusResponseSchema
|
|
12425
|
+
},
|
|
11858
12426
|
Login: {
|
|
11859
12427
|
Request: LoginRequestSchema,
|
|
11860
12428
|
Response: LoginResponseSchema
|
|
@@ -11887,8 +12455,13 @@ userRouter.get("/id", async (_req, res) => {
|
|
|
11887
12455
|
}
|
|
11888
12456
|
});
|
|
11889
12457
|
userRouter.post("/email", async (req, res) => {
|
|
12458
|
+
const bodyResult = UserSchemas.Update.Request.safeParse(req.body);
|
|
12459
|
+
if (!bodyResult.success) {
|
|
12460
|
+
res.status(400).json({ error: z.prettifyError(bodyResult.error) });
|
|
12461
|
+
return;
|
|
12462
|
+
}
|
|
12463
|
+
const { email } = bodyResult.data;
|
|
11890
12464
|
try {
|
|
11891
|
-
const { email } = UserSchemas.Update.Request.parse(req.body);
|
|
11892
12465
|
setUserEmail(email);
|
|
11893
12466
|
res.json(UserSchemas.Update.Response.parse({
|
|
11894
12467
|
success: true,
|
|
@@ -11902,8 +12475,7 @@ userRouter.post("/email", async (req, res) => {
|
|
|
11902
12475
|
await telemetry_default.saveConsent(email, { source: "webui_redteam" });
|
|
11903
12476
|
} catch (error) {
|
|
11904
12477
|
logger_default.error(`Error setting email: ${error}`);
|
|
11905
|
-
|
|
11906
|
-
else res.status(500).json({ error: String(error) });
|
|
12478
|
+
res.status(500).json({ error: "Failed to update email" });
|
|
11907
12479
|
}
|
|
11908
12480
|
});
|
|
11909
12481
|
userRouter.put("/email/clear", async (_req, res) => {
|
|
@@ -11920,7 +12492,8 @@ userRouter.put("/email/clear", async (_req, res) => {
|
|
|
11920
12492
|
});
|
|
11921
12493
|
userRouter.get("/email/status", async (req, res) => {
|
|
11922
12494
|
try {
|
|
11923
|
-
const
|
|
12495
|
+
const { validate } = UserSchemas.EmailStatus.Query.parse(req.query);
|
|
12496
|
+
const result = await checkEmailStatus({ validate });
|
|
11924
12497
|
res.json(UserSchemas.EmailStatus.Response.parse({
|
|
11925
12498
|
hasEmail: result.hasEmail,
|
|
11926
12499
|
email: result.email,
|
|
@@ -11929,13 +12502,17 @@ userRouter.get("/email/status", async (req, res) => {
|
|
|
11929
12502
|
}));
|
|
11930
12503
|
} catch (error) {
|
|
11931
12504
|
logger_default.error(`Error checking email status: ${error}`);
|
|
11932
|
-
|
|
11933
|
-
else res.status(500).json({ error: "Failed to check email status" });
|
|
12505
|
+
res.status(500).json({ error: "Failed to check email status" });
|
|
11934
12506
|
}
|
|
11935
12507
|
});
|
|
11936
12508
|
userRouter.post("/login", async (req, res) => {
|
|
12509
|
+
const bodyResult = UserSchemas.Login.Request.safeParse(req.body);
|
|
12510
|
+
if (!bodyResult.success) {
|
|
12511
|
+
res.status(400).json({ error: z.prettifyError(bodyResult.error) });
|
|
12512
|
+
return;
|
|
12513
|
+
}
|
|
12514
|
+
const { apiKey, apiHost } = bodyResult.data;
|
|
11937
12515
|
try {
|
|
11938
|
-
const { apiKey, apiHost } = UserSchemas.Login.Request.parse(req.body);
|
|
11939
12516
|
const host = apiHost || cloudConfig.getApiHost();
|
|
11940
12517
|
const { user, organization, app } = await cloudConfig.validateAndSetApiToken(apiKey, host);
|
|
11941
12518
|
const existingEmail = getUserEmail();
|
|
@@ -11962,8 +12539,7 @@ userRouter.post("/login", async (req, res) => {
|
|
|
11962
12539
|
}));
|
|
11963
12540
|
} catch (error) {
|
|
11964
12541
|
logger_default.error(`Error during API key login: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
11965
|
-
|
|
11966
|
-
else res.status(401).json({ error: "Invalid API key or authentication failed" });
|
|
12542
|
+
res.status(401).json({ error: "Invalid API key or authentication failed" });
|
|
11967
12543
|
}
|
|
11968
12544
|
});
|
|
11969
12545
|
userRouter.post("/logout", async (_req, res) => {
|
|
@@ -11995,6 +12571,32 @@ userRouter.get("/cloud-config", async (_req, res) => {
|
|
|
11995
12571
|
}
|
|
11996
12572
|
});
|
|
11997
12573
|
|
|
12574
|
+
//#endregion
|
|
12575
|
+
//#region src/types/api/version.ts
|
|
12576
|
+
const VersionResponseSchema = z.object({
|
|
12577
|
+
currentVersion: z.string(),
|
|
12578
|
+
latestVersion: z.string(),
|
|
12579
|
+
updateAvailable: z.boolean(),
|
|
12580
|
+
selfHosted: z.boolean(),
|
|
12581
|
+
isNpx: z.boolean(),
|
|
12582
|
+
updateCommands: z.object({
|
|
12583
|
+
primary: z.string(),
|
|
12584
|
+
alternative: z.string().nullable(),
|
|
12585
|
+
commandType: z.enum([
|
|
12586
|
+
"docker",
|
|
12587
|
+
"npx",
|
|
12588
|
+
"npm"
|
|
12589
|
+
])
|
|
12590
|
+
}),
|
|
12591
|
+
commandType: z.enum([
|
|
12592
|
+
"docker",
|
|
12593
|
+
"npx",
|
|
12594
|
+
"npm"
|
|
12595
|
+
])
|
|
12596
|
+
});
|
|
12597
|
+
/** Grouped schemas for server-side validation. */
|
|
12598
|
+
const VersionSchemas = { Response: VersionResponseSchema };
|
|
12599
|
+
|
|
11998
12600
|
//#endregion
|
|
11999
12601
|
//#region src/updates/updateCommands.ts
|
|
12000
12602
|
function getUpdateCommands(options) {
|
|
@@ -12073,7 +12675,7 @@ router.get("/", async (_req, res) => {
|
|
|
12073
12675
|
updateCommands,
|
|
12074
12676
|
commandType: updateCommands.commandType
|
|
12075
12677
|
};
|
|
12076
|
-
res.json(response);
|
|
12678
|
+
res.json(VersionSchemas.Response.parse(response));
|
|
12077
12679
|
} catch (error) {
|
|
12078
12680
|
logger_default.error(`Error in version check endpoint: ${error}`);
|
|
12079
12681
|
const selfHosted = getEnvBool("PROMPTFOO_SELF_HOSTED");
|
|
@@ -12147,6 +12749,7 @@ function createApp() {
|
|
|
12147
12749
|
const app = express();
|
|
12148
12750
|
const staticDir = findStaticDir();
|
|
12149
12751
|
app.use(cors());
|
|
12752
|
+
app.use(csrfProtection);
|
|
12150
12753
|
app.use(compression());
|
|
12151
12754
|
app.use(express.json({ limit: REQUEST_SIZE_LIMIT }));
|
|
12152
12755
|
app.use(express.urlencoded({
|
|
@@ -14250,8 +14853,8 @@ async function createDummyFiles(directory, interactive = true) {
|
|
|
14250
14853
|
]
|
|
14251
14854
|
},
|
|
14252
14855
|
{
|
|
14253
|
-
name: "[Google] Gemini
|
|
14254
|
-
value: ["vertex:gemini-2.5-pro"]
|
|
14856
|
+
name: "[Google] Gemini 3.1 Pro, ...",
|
|
14857
|
+
value: ["vertex:gemini-3.1-pro-preview", "vertex:gemini-2.5-pro"]
|
|
14255
14858
|
},
|
|
14256
14859
|
{
|
|
14257
14860
|
name: "[HuggingFace] Llama, Phi, Gemma, ...",
|
|
@@ -14583,8 +15186,8 @@ function shouldUseInkList() {
|
|
|
14583
15186
|
async function runInkList(options) {
|
|
14584
15187
|
const [React, { renderInteractive }, { ListApp }] = await Promise.all([
|
|
14585
15188
|
import("react"),
|
|
14586
|
-
import("./render-
|
|
14587
|
-
import("./ListApp-
|
|
15189
|
+
import("./render-CAZvKKkB.js"),
|
|
15190
|
+
import("./ListApp-D3DG0F8h.js")
|
|
14588
15191
|
]);
|
|
14589
15192
|
let result = { cancelled: false };
|
|
14590
15193
|
let resolveResult;
|
|
@@ -16903,8 +17506,8 @@ function registerRunEvaluationTool(server) {
|
|
|
16903
17506
|
}
|
|
16904
17507
|
filteredTestSuite.tests = filteredTests;
|
|
16905
17508
|
}
|
|
16906
|
-
const { evaluate } = await import("./evaluator-
|
|
16907
|
-
const evalRecord = await (await import("./eval-
|
|
17509
|
+
const { evaluate } = await import("./evaluator-0PvfeBYh.js");
|
|
17510
|
+
const evalRecord = await (await import("./eval-CKHWqG9f.js")).default.create(config, filteredTestSuite.prompts, { id: `mcp-eval-${Date.now()}` });
|
|
16908
17511
|
logger_default.debug(`Running filtered eval with ${filteredTestSuite.tests?.length || 0} test cases, ${filteredTestSuite.prompts.length} prompts, ${filteredTestSuite.providers.length} providers`);
|
|
16909
17512
|
const startTime = Date.now();
|
|
16910
17513
|
const result = await evaluate(filteredTestSuite, evalRecord, {
|
|
@@ -17666,6 +18269,7 @@ async function handleEval(id) {
|
|
|
17666
18269
|
const eval_ = await Eval.findById(id);
|
|
17667
18270
|
if (!eval_) {
|
|
17668
18271
|
logger_default.error(`No evaluation found with ID ${id}`);
|
|
18272
|
+
logger_default.info(`Run ${chalk.green("promptfoo list evals")} to see available evaluation IDs.`);
|
|
17669
18273
|
process.exitCode = 1;
|
|
17670
18274
|
return;
|
|
17671
18275
|
}
|
|
@@ -17722,6 +18326,7 @@ async function showCommand(program) {
|
|
|
17722
18326
|
const latestEval = await Eval.latest();
|
|
17723
18327
|
if (latestEval) return handleEval(latestEval.id);
|
|
17724
18328
|
logger_default.error("No eval found");
|
|
18329
|
+
logger_default.info(`Run ${chalk.green("promptfoo eval")} to create one.`);
|
|
17725
18330
|
process.exitCode = 1;
|
|
17726
18331
|
return;
|
|
17727
18332
|
}
|
|
@@ -17729,6 +18334,7 @@ async function showCommand(program) {
|
|
|
17729
18334
|
if (await getPromptFromHash(id)) return handlePrompt(id);
|
|
17730
18335
|
if (await getDatasetFromHash(id)) return handleDataset(id);
|
|
17731
18336
|
logger_default.error(`No resource found with ID ${id}`);
|
|
18337
|
+
logger_default.info(`Run ${chalk.green("promptfoo list evals")}, ${chalk.green("promptfoo list prompts")}, or ${chalk.green("promptfoo list datasets")} to see available IDs.`);
|
|
17732
18338
|
process.exitCode = 1;
|
|
17733
18339
|
});
|
|
17734
18340
|
showCommand.command("eval [id]").description("Show details of a specific evaluation (defaults to most recent)").action(async (id) => {
|
|
@@ -17736,6 +18342,7 @@ async function showCommand(program) {
|
|
|
17736
18342
|
const latestEval = await Eval.latest();
|
|
17737
18343
|
if (latestEval) return handleEval(latestEval.id);
|
|
17738
18344
|
logger_default.error("No eval found");
|
|
18345
|
+
logger_default.info(`Run ${chalk.green("promptfoo eval")} to create one.`);
|
|
17739
18346
|
process.exitCode = 1;
|
|
17740
18347
|
return;
|
|
17741
18348
|
}
|