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.
Files changed (237) hide show
  1. package/README.md +35 -22
  2. package/dist/src/{ListApp-CYTAaS6A.js → ListApp-D3DG0F8h.js} +1 -1
  3. package/dist/src/{accounts-CwqY22lU.cjs → accounts-BzEY8H3v.cjs} +4 -4
  4. package/dist/src/{accounts-Bzx8GnL6.js → accounts-DHHiXsy6.js} +5 -5
  5. package/dist/src/{accounts-BSwMxhY3.js → accounts-R3gfCR_g.js} +4 -4
  6. package/dist/src/{agentic-utils-5K7LzooV.js → agentic-utils-D6_gzOUF.js} +2 -2
  7. package/dist/src/{agents-B8RWrA68.cjs → agents-C7BiinFI.cjs} +6 -6
  8. package/dist/src/{agents-zHyWnnQ2.js → agents-Cao4i7AX.js} +7 -7
  9. package/dist/src/{agents-KZxXwlyn.cjs → agents-CwM7re15.cjs} +2 -2
  10. package/dist/src/{agents-DleC7rb4.js → agents-DETIQHqF.js} +6 -6
  11. package/dist/src/{agents-CkA9l77-.js → agents-DYIT-hQy.js} +5 -5
  12. package/dist/src/{agents-B20gl7hH.js → agents-GiUianme.js} +2 -2
  13. package/dist/src/{aimlapi-DgbFGPTX.js → aimlapi-CMJpKK-B.js} +9 -9
  14. package/dist/src/{aimlapi-DILJovhN.js → aimlapi-DMF6v_vb.js} +14 -14
  15. package/dist/src/{aimlapi-DIdWBHtH.cjs → aimlapi-DoGLcQW_.cjs} +9 -9
  16. package/dist/src/app/assets/index-BOgkICuY.css +1 -0
  17. package/dist/src/app/assets/index-CSgqn_Vd.js +428 -0
  18. package/dist/src/app/assets/{vendor-charts-DnwydkkY.js → vendor-charts-CYyo8R8v.js} +1 -1
  19. package/dist/src/app/assets/{vendor-markdown-DjKAU15k.js → vendor-markdown-DSmzq4Jh.js} +1 -1
  20. package/dist/src/app/assets/vendor-react-AtKqiNEf.js +4 -0
  21. package/dist/src/app/assets/{vendor-syntax-Dc86c4Bu.js → vendor-syntax-D06x6TQF.js} +1 -1
  22. package/dist/src/app/index.html +6 -6
  23. package/dist/src/app/tsconfig.app.tsbuildinfo +1 -1
  24. package/dist/src/{audio-BoR9KZV8.cjs → audio-BWjyvHn9.cjs} +3 -3
  25. package/dist/src/{audio-nUSOpXY7.js → audio-BrJBFN2b.js} +3 -3
  26. package/dist/src/{audio-BDrAkpVc.js → audio-DUH4q0Xq.js} +4 -4
  27. package/dist/src/{base-DumUXbOH.js → base-BaXmtXYp.js} +2 -2
  28. package/dist/src/{base-CwCV0t8r.js → base-Dtp8b4_N.js} +2 -2
  29. package/dist/src/{base-D8VQYhNQ.cjs → base-f71xxWai.cjs} +2 -2
  30. package/dist/src/{blobs-_kNRY6vU.cjs → blobs-673H0jCl.cjs} +2 -2
  31. package/dist/src/{blobs-Dnt2rxpc.js → blobs-C9J2mVgo.js} +2 -2
  32. package/dist/src/{blobs-DlB5fwyF.js → blobs-kt8v3UyH.js} +2 -2
  33. package/dist/src/{cache-S9MAoF9H.cjs → cache-7xULbvt3.cjs} +34 -7
  34. package/dist/src/{cache-C4A4H4Xb.js → cache-BLLayYEN.js} +23 -8
  35. package/dist/src/{cache-B2oIvf92.js → cache-BUPcq0Ad.js} +2 -2
  36. package/dist/src/cache-CVfRb-HD.cjs +6 -0
  37. package/dist/src/{cache-DofebEnH.js → cache-O4EuX2JV.js} +3 -3
  38. package/dist/src/{cache-Bg1Wpvzq.js → cache-mIszOnuz.js} +4 -21
  39. package/dist/src/{chat-DTCPbuld.cjs → chat-C4zqjObh.cjs} +6 -6
  40. package/dist/src/{chat-noXtEAh5.js → chat-C8Ei4f87.js} +6 -6
  41. package/dist/src/{chat-BiImdLJf.js → chat-CgyGj2hC.js} +8 -8
  42. package/dist/src/{chat-DQ6Z5Cbn.js → chat-Cpz3O-Xl.js} +13 -13
  43. package/dist/src/{chat-D_cro0wd.js → chat-DIywASPG.js} +10 -10
  44. package/dist/src/{chat-BR2x1dG_.cjs → chat-Fl6TZJRS.cjs} +8 -8
  45. package/dist/src/{chatkit-TnC1YoVi.js → chatkit-DEls11hE.js} +3 -3
  46. package/dist/src/{claude-agent-sdk-CBpAZV5h.js → claude-agent-sdk-6-xTaLwM.js} +45 -16
  47. package/dist/src/{claude-agent-sdk-uHLgAUZj.cjs → claude-agent-sdk-BzxF6NIJ.cjs} +45 -16
  48. package/dist/src/{claude-agent-sdk-qxE5mGVi.js → claude-agent-sdk-CmkTnKGH.js} +48 -19
  49. package/dist/src/{claude-agent-sdk-DyPvrj-1.js → claude-agent-sdk-rXCBLK_o.js} +38 -9
  50. package/dist/src/{cloud-lRosK6Nk.js → cloud-BMbRVJFw.js} +2 -2
  51. package/dist/src/{cloud-DHWE-9nj.js → cloud-CZ4hytdm.js} +1 -1
  52. package/dist/src/{cloudflare-ai-D0OtFY6g.cjs → cloudflare-ai-BAQ0u_dg.cjs} +9 -9
  53. package/dist/src/{cloudflare-ai-BprwSD6d.js → cloudflare-ai-CUg4BTcj.js} +9 -9
  54. package/dist/src/{cloudflare-ai-CB7OWRAP.js → cloudflare-ai-CobxMTR4.js} +14 -14
  55. package/dist/src/{cloudflare-gateway-C29gWj5O.cjs → cloudflare-gateway-C0sgfr_z.cjs} +10 -10
  56. package/dist/src/{cloudflare-gateway-BaVwoBjE.js → cloudflare-gateway-D2_yi-Fh.js} +15 -15
  57. package/dist/src/{cloudflare-gateway-BjoxwqUw.js → cloudflare-gateway-_itGuXry.js} +10 -10
  58. package/dist/src/{codex-sdk-DNkCbb7c.js → codex-sdk-dSnGdgIp.js} +3 -3
  59. package/dist/src/{cometapi-BGxGZ7VA.js → cometapi-B01btbfb.js} +15 -15
  60. package/dist/src/{cometapi-xiTQZp8R.js → cometapi-ChAaRjg5.js} +10 -10
  61. package/dist/src/{cometapi-DalITP94.cjs → cometapi-JbvOJSCO.cjs} +10 -10
  62. package/dist/src/{completion-BTbouQDz.js → completion-BBJ6zmG3.js} +5 -5
  63. package/dist/src/{completion-HYrX5gEL.js → completion-D9_MDlnd.js} +4 -4
  64. package/dist/src/{completion-DV0Ys99K.cjs → completion-DDyL3Cb2.cjs} +4 -4
  65. package/dist/src/{docker-Dx_FN1ag.js → docker-BuButc4D.js} +14 -14
  66. package/dist/src/{docker-zUAnbYTM.cjs → docker-Ckw-j7Rr.cjs} +9 -9
  67. package/dist/src/{docker-BAYnlbAW.js → docker-JAAubMw3.js} +9 -9
  68. package/dist/src/errors-DnGCbnx8.js +36 -0
  69. package/dist/src/{esm-79BpbQzg.js → esm-CYhseqj4.js} +1 -1
  70. package/dist/src/eval-CKHWqG9f.js +16 -0
  71. package/dist/src/{eval-Cxb9alW8.js → eval-CYrbG57o.js} +45 -13
  72. package/dist/src/{evalResult-B8LbgWMA.js → evalResult-6JaUIStC.js} +6 -6
  73. package/dist/src/{evalResult-CJNSpa3q.js → evalResult-COsVttMA.js} +6 -6
  74. package/dist/src/evalResult-CxTP-LMm.cjs +11 -0
  75. package/dist/src/evalResult-CzLURDcP.js +13 -0
  76. package/dist/src/{evalResult-hI1mhi_g.cjs → evalResult-DlRfu_Rq.cjs} +6 -6
  77. package/dist/src/evalResult-DyttNQ_G.js +11 -0
  78. package/dist/src/evaluator-0PvfeBYh.js +38 -0
  79. package/dist/src/{evaluator-1Tkdu8MK.js → evaluator-3EJCMTs0.js} +74 -48
  80. package/dist/src/{extractor-BPBlLyOn.cjs → extractor-DbhlYEeo.cjs} +22 -5
  81. package/dist/src/{extractor-CuUWqwrE.js → extractor-Hs7la_19.js} +22 -5
  82. package/dist/src/{extractor-BFoThUOa.js → extractor-LSYjrhK0.js} +22 -5
  83. package/dist/src/{fetch-zJuFPxN1.js → fetch-18MuNu9i.js} +4 -4
  84. package/dist/src/{fetch-BdiCecAv.js → fetch-Bi0o-fdp.js} +1 -1
  85. package/dist/src/fetch-CMptBDVg.cjs +4 -0
  86. package/dist/src/{fetch-BQgz6x7S.js → fetch-DAZkv3gV.js} +2 -2
  87. package/dist/src/{fetch-BuP-gm0E.js → fetch-SRsE6Ctl.js} +4 -4
  88. package/dist/src/{fetch-Dy2YML9G.cjs → fetch-ZMn_oemb.cjs} +4 -4
  89. package/dist/src/{fileExtensions-CzcPkiKZ.js → fileExtensions-BpuMmaFL.js} +1 -1
  90. package/dist/src/{formatDuration-CGa0ZDQ8.js → formatDuration-Doo0xq-z.js} +1 -1
  91. package/dist/src/{genaiTracer-foKtbVa2.js → genaiTracer-CqNnnXrE.js} +1 -1
  92. package/dist/src/graders-BCytzXrb.js +34 -0
  93. package/dist/src/{graders-DMAzEgEF.js → graders-BaMCwIKp.js} +211 -91
  94. package/dist/src/graders-CGZQShfJ.cjs +33 -0
  95. package/dist/src/{graders-NBsuYeMx.cjs → graders-DzUUnUjC.cjs} +206 -86
  96. package/dist/src/{graders-CtvuaC8D.js → graders-QsALpIdy.js} +206 -86
  97. package/dist/src/graders-spkuVC-E.js +36 -0
  98. package/dist/src/{image-Bf8RiJ2g.js → image-BXt_7u0v.js} +3 -3
  99. package/dist/src/{image-LhLO5KCW.js → image-BiEVdpdP.js} +4 -4
  100. package/dist/src/{image-CaVWfZub.cjs → image-C3BjJUAU.cjs} +4 -4
  101. package/dist/src/{image-BnF25Osq.cjs → image-D10zEe1f.cjs} +3 -3
  102. package/dist/src/{image-DIn_AW1Z.js → image-DB4sHxdJ.js} +4 -4
  103. package/dist/src/{image-DFwp1aAV.js → image-mhAGP07h.js} +6 -6
  104. package/dist/src/index.cjs +152 -69
  105. package/dist/src/index.d.cts +20 -3
  106. package/dist/src/index.d.ts +20 -3
  107. package/dist/src/index.js +152 -69
  108. package/dist/src/{interactiveCheck-Dmm9vjpx.js → interactiveCheck-DU-MAhp5.js} +1 -1
  109. package/dist/src/{knowledgeBase-Bf57PI7g.cjs → knowledgeBase-Bnb00xKs.cjs} +6 -6
  110. package/dist/src/{knowledgeBase-DZw2L9kf.js → knowledgeBase-CMvMlLZR.js} +8 -8
  111. package/dist/src/{knowledgeBase-CfOvyF2s.js → knowledgeBase-XJQ0Qyez.js} +6 -6
  112. package/dist/src/{litellm-Ds8YmO1b.js → litellm-BECdjOTx.js} +3 -3
  113. package/dist/src/{litellm-ByNRN2Vh.cjs → litellm-BrnZhMcL.cjs} +11 -11
  114. package/dist/src/{litellm-fU-xV6T4.js → litellm-CHrRmPAe.js} +16 -16
  115. package/dist/src/{litellm-DexU8FFp.js → litellm-CrLJrPIm.js} +11 -11
  116. package/dist/src/{luma-ray-y26EHFO8.js → luma-ray-BOeX-h0M.js} +8 -8
  117. package/dist/src/{luma-ray-BIe5ctI4.cjs → luma-ray-C0RkI3lt.cjs} +8 -8
  118. package/dist/src/{luma-ray-vvE-O9EA.js → luma-ray-DgKLS0BF.js} +10 -10
  119. package/dist/src/main.js +1011 -404
  120. package/dist/src/{messages-Bg2ekFPc.js → messages-CDZYGNlS.js} +7 -7
  121. package/dist/src/{messages-DpoCLD_8.js → messages-D61tPFQo.js} +9 -9
  122. package/dist/src/{messages-16jb9LP7.cjs → messages-DXV3Qh8_.cjs} +7 -7
  123. package/dist/src/{meteor-DLCdreag.js → meteor-P2rUE-Uz.js} +1 -1
  124. package/dist/src/{nova-reel-BM0snD2m.js → nova-reel-Bk5npr2q.js} +10 -10
  125. package/dist/src/{nova-reel-CvXCVn3f.cjs → nova-reel-D9FXq3Mt.cjs} +8 -8
  126. package/dist/src/{nova-reel-CJLpBKSI.js → nova-reel-DtCjbD5O.js} +8 -8
  127. package/dist/src/{nova-sonic-CklvraBd.js → nova-sonic-BXRfQyF-.js} +7 -7
  128. package/dist/src/{nova-sonic-DEx_FdFW.cjs → nova-sonic-BoRSY_U6.cjs} +5 -5
  129. package/dist/src/{nova-sonic-Cjh_qopr.js → nova-sonic-CgaWLDM1.js} +5 -5
  130. package/dist/src/{openai-jNIREoAW.js → openai-CT5fwbve.js} +1 -1
  131. package/dist/src/{openclaw-CnAwBfPO.js → openclaw-B6XY2kUf.js} +14 -14
  132. package/dist/src/{openclaw-DBSSY4SP.js → openclaw-CpPrXwf6.js} +9 -9
  133. package/dist/src/{openclaw-CbHtt3B8.cjs → openclaw-DDSfq5fp.cjs} +9 -9
  134. package/dist/src/{opencode-sdk-CqxTHrk0.js → opencode-sdk-CImWVqy9.js} +6 -6
  135. package/dist/src/{opencode-sdk-xUrRk5Jx.js → opencode-sdk-CuCztr4P.js} +4 -4
  136. package/dist/src/{opencode-sdk-DdmtRB6D.cjs → opencode-sdk-mqF-Oj3f.cjs} +4 -4
  137. package/dist/src/{otlpReceiver-th2zHwjZ.cjs → otlpReceiver-B6Xo4KZM.cjs} +4 -4
  138. package/dist/src/{otlpReceiver-uytrRKdo.js → otlpReceiver-BO0rbDzh.js} +4 -4
  139. package/dist/src/{otlpReceiver-C-1cdVDr.js → otlpReceiver-Dg817agV.js} +5 -5
  140. package/dist/src/{providerRegistry-DAZoFmGv.js → providerRegistry-Xf0qdqGQ.js} +1 -1
  141. package/dist/src/providers-BMZZmPBJ.cjs +32 -0
  142. package/dist/src/{providers-CpUrPa8s.cjs → providers-BNKVY53V.cjs} +94 -64
  143. package/dist/src/{providers-D3lk_R7Q.js → providers-BiNq_Iyc.js} +94 -64
  144. package/dist/src/{providers-BOvl9qv5.js → providers-BlEhY5mi.js} +110 -79
  145. package/dist/src/providers-CQQrNaJk.js +32 -0
  146. package/dist/src/providers-Ck8HyrC-.js +34 -0
  147. package/dist/src/{pythonUtils-CUPlf1H0.js → pythonUtils-r1uBuA0n.js} +2 -2
  148. package/dist/src/{quiverai-BNGJOQym.cjs → quiverai-BNfIwKCO.cjs} +8 -8
  149. package/dist/src/{quiverai-Y7Is4wah.js → quiverai-BQigKdIH.js} +13 -13
  150. package/dist/src/{quiverai-DthRqZs9.js → quiverai-Bfy2WnE2.js} +8 -8
  151. package/dist/src/{render-kFatbqG6.js → render-CAZvKKkB.js} +3 -3
  152. package/dist/src/{responses-BtU_i-R0.js → responses-DLLjADw5.js} +5 -5
  153. package/dist/src/{responses-B85halqh.js → responses-TsdODUpm.js} +8 -8
  154. package/dist/src/{responses-CXJACENh.cjs → responses-zOtKtnY_.cjs} +5 -5
  155. package/dist/src/{rubyUtils-DYKoa7Ze.js → rubyUtils-BtjlqyXt.js} +2 -2
  156. package/dist/src/{rubyUtils-BA4D4gMX.js → rubyUtils-D7--T12C.js} +2 -2
  157. package/dist/src/{sagemaker-hRS_Jj3-.js → sagemaker-BcgLu0U4.js} +17 -17
  158. package/dist/src/{sagemaker-DI6IOTjT.js → sagemaker-CLdUAv5z.js} +11 -11
  159. package/dist/src/{sagemaker-BUagsrJ-.cjs → sagemaker-DwNnEVYt.cjs} +11 -11
  160. package/dist/src/{scanner-C6QXA9Pn.js → scanner-Dyw21Wg_.js} +10 -10
  161. package/dist/src/server/index.js +1376 -486
  162. package/dist/src/server-BUbS0Qfh.js +6 -0
  163. package/dist/src/{server-BPC37wlX.js → server-CbMTRQkg.js} +3 -3
  164. package/dist/src/{server-C_O6-VE4.cjs → server-CgUQ25qW.cjs} +3 -3
  165. package/dist/src/{server-HQtz_gUy.js → server-DWmZLfCy.js} +4 -4
  166. package/dist/src/server-XpGXFHkS.cjs +6 -0
  167. package/dist/src/server-gfOx5Zrk.js +8 -0
  168. package/dist/src/{signal-D4VhSJOY.js → signal-Bl32q42d.js} +2 -2
  169. package/dist/src/{slack-DTUSadx4.js → slack-BfdBx2tO.js} +1 -1
  170. package/dist/src/{store-BTdkdmK9.js → store-2ocbYY9D.js} +2 -2
  171. package/dist/src/store-5u2yriTV.js +7 -0
  172. package/dist/src/{store-F-KTiqfb.cjs → store-BqwfFEyF.cjs} +2 -2
  173. package/dist/src/{store-rueRdzcm.js → store-D4gdn9ih.js} +2 -2
  174. package/dist/src/store-D_lq_8oQ.js +6 -0
  175. package/dist/src/store-m5KT6Ly7.cjs +6 -0
  176. package/dist/src/{tables-w-r-abWq.cjs → tables-B9E1kRp-.cjs} +2 -2
  177. package/dist/src/{tables-ChjAuVAR.js → tables-C7TT2XVn.js} +2 -2
  178. package/dist/src/{tables-dNaVPyDr.js → tables-D-NSwNIb.js} +2 -2
  179. package/dist/src/telemetry-5RHFoCJh.js +6 -0
  180. package/dist/src/{telemetry-wXyCQ1Nz.js → telemetry-BXyVqyAg.js} +3 -3
  181. package/dist/src/{telemetry-Y1esJM0v.cjs → telemetry-D0_yFdtU.cjs} +3 -3
  182. package/dist/src/{telemetry-Dai7PdlR.js → telemetry-DZ_7PaVq.js} +3 -3
  183. package/dist/src/telemetry-Do8wMnA-.js +8 -0
  184. package/dist/src/telemetry-LojxPoFq.cjs +6 -0
  185. package/dist/src/{text-C1cXk9ij.js → text-DgMr_tiM.js} +1 -1
  186. package/dist/src/{tokenUsageUtils-D2ko2eFQ.js → tokenUsageUtils-DmZSD2eU.js} +1 -1
  187. package/dist/src/{transcription-i6EfSMie.js → transcription-CYuY5sFO.js} +6 -6
  188. package/dist/src/{transcription-CcYTL4wi.js → transcription-Ch7S-LWw.js} +10 -10
  189. package/dist/src/{transcription-C-K2hDJX.cjs → transcription-FNIz3YOe.cjs} +6 -6
  190. package/dist/src/{transform-gpzlPrwG.js → transform-CoP2bJ7P.js} +43 -4
  191. package/dist/src/{transform-B86_0IIb.js → transform-CqTFr7KR.js} +4 -4
  192. package/dist/src/{transform-DosdxVdh.js → transform-DMaxQwDx.js} +43 -4
  193. package/dist/src/{transform-DwsntMEQ.js → transform-GybT0X0u.js} +3 -3
  194. package/dist/src/{transform-CCSNqTI-.cjs → transform-ivxEY4f7.cjs} +54 -3
  195. package/dist/src/{transformersAvailability-DM54tWzt.js → transformersAvailability-DwmezkVe.js} +1 -1
  196. package/dist/src/{types-Brw5kggg.cjs → types-BIfttHrT.cjs} +13 -1
  197. package/dist/src/{types-CslP2TyG.js → types-DMVjYLpx.js} +13 -1
  198. package/dist/src/{types-CMDs7Kzu.js → types-t52w-XsS.js} +16 -2
  199. package/dist/src/{util-BECSJ0T_.js → util-BSh4a_Q8.js} +6 -6
  200. package/dist/src/{util-D8z5QOy8.cjs → util-C08Kns6-.cjs} +3 -3
  201. package/dist/src/{util-CttPoePn.js → util-CUEt0Vum.js} +3 -3
  202. package/dist/src/{util-CfPdWQJX.js → util-Cl0zfT3V.js} +3 -3
  203. package/dist/src/{util-Dab5xp2E.cjs → util-DUYOvxAy.cjs} +3 -3
  204. package/dist/src/{util-CY_XguB9.js → util-DiCePfDu.js} +3 -3
  205. package/dist/src/{util-CBAXTxfy.cjs → util-DkFTvieG.cjs} +3 -3
  206. package/dist/src/{util-Dft1RxsL.js → util-mJ58qbbw.js} +3 -3
  207. package/dist/src/{util-Ba18wQ8e.js → util-vjscpUzy.js} +3 -3
  208. package/dist/src/{utils-BTeLHwiH.js → utils-CVzb4YiI.js} +2 -2
  209. package/dist/src/{utils-Db5KwiXp.cjs → utils-DFaZa6Rf.cjs} +2 -2
  210. package/dist/src/{utils-ICP7kLcY.js → utils-JaY9veb5.js} +2 -2
  211. package/dist/tsconfig.tsbuildinfo +1 -1
  212. package/package.json +43 -42
  213. package/dist/src/app/assets/index-COIipVf1.css +0 -1
  214. package/dist/src/app/assets/index-ClD64gUa.js +0 -428
  215. package/dist/src/app/assets/vendor-react-DHoaNLma.js +0 -4
  216. package/dist/src/cache-BJ31KhNT.cjs +0 -6
  217. package/dist/src/eval-DgiEH2xe.js +0 -16
  218. package/dist/src/evalResult-Boel8xoS.js +0 -13
  219. package/dist/src/evalResult-DGx1-Ela.js +0 -11
  220. package/dist/src/evalResult-DzRxYLDH.cjs +0 -11
  221. package/dist/src/evaluator-B4qKt437.js +0 -38
  222. package/dist/src/fetch-DwhKew9e.cjs +0 -4
  223. package/dist/src/graders-BpEqZT2Q.js +0 -34
  224. package/dist/src/graders-DTGI-hHE.cjs +0 -33
  225. package/dist/src/graders-DgMr-E4I.js +0 -36
  226. package/dist/src/providers-DVsoSup2.js +0 -34
  227. package/dist/src/providers-D_tJatIk.js +0 -32
  228. package/dist/src/providers-wpMPsMZ7.cjs +0 -32
  229. package/dist/src/server-BeqUuSvj.js +0 -6
  230. package/dist/src/server-CwQsQ6dl.cjs +0 -6
  231. package/dist/src/server-Dhbq83-d.js +0 -8
  232. package/dist/src/store-C7U17qcu.js +0 -6
  233. package/dist/src/store-CuDU_jDN.js +0 -7
  234. package/dist/src/store-awdDxuiD.cjs +0 -6
  235. package/dist/src/telemetry-ClcYTCOG.js +0 -8
  236. package/dist/src/telemetry-DEWuLu_s.js +0 -6
  237. 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-BuP-gm0E.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-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-Bzx8GnL6.js";
7
- import { a as readGlobalConfig, i as cloudConfig, r as CloudConfig, s as writeGlobalConfigPartial } from "./cloud-lRosK6Nk.js";
8
- import { n as TelemetryEventSchema, r as telemetry_default } from "./telemetry-wXyCQ1Nz.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-CMDs7Kzu.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-HQtz_gUy.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-BOvl9qv5.js";
12
- import { a as fetchWithCache, n as clearCache, r as disableCache, t as cache_exports } from "./cache-Bg1Wpvzq.js";
13
- import { i as isJavascriptFile } from "./fileExtensions-CzcPkiKZ.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-BECSJ0T_.js";
15
- import { r as importModule, t as getDirectory } from "./esm-79BpbQzg.js";
16
- import { r as runPython } from "./pythonUtils-CUPlf1H0.js";
17
- import "./transform-B86_0IIb.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-DMAzEgEF.js";
19
- import { i as generateIdFromPrompt } from "./utils-ICP7kLcY.js";
20
- import { a as sha256, i as randomSequence, n as isBlobStorageEnabled } from "./extractor-BFoThUOa.js";
21
- import "./genaiTracer-foKtbVa2.js";
22
- import "./chat-D_cro0wd.js";
23
- import { a as createEmptyTokenUsage, i as createEmptyAssertions, n as accumulateResponseTokenUsage, r as accumulateTokenUsage, t as accumulateAssertionTokenUsage } from "./tokenUsageUtils-D2ko2eFQ.js";
24
- import "./transform-gpzlPrwG.js";
25
- import "./messages-DpoCLD_8.js";
26
- import "./util-Ba18wQ8e.js";
27
- import "./responses-B85halqh.js";
28
- import "./openai-jNIREoAW.js";
29
- import "./util-CttPoePn.js";
30
- import "./completion-BTbouQDz.js";
31
- import { n as getBlobUrl, t as getBlobByHash } from "./blobs-DlB5fwyF.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-ChjAuVAR.js";
33
- import { n as escapeRegExp, t as ellipsize } from "./text-C1cXk9ij.js";
34
- import { n as getTraceStore } from "./store-rueRdzcm.js";
35
- import "./base-DumUXbOH.js";
36
- import "./image-DFwp1aAV.js";
37
- import "./providerRegistry-DAZoFmGv.js";
38
- import "./rubyUtils-DYKoa7Ze.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-1Tkdu8MK.js";
40
- import { n as setupSignalWatcher, t as readSignalEvalId } from "./signal-D4VhSJOY.js";
41
- import { a as createEvalId, n as EvalQueries, s as getEvalSummaries, t as Eval } from "./eval-Cxb9alW8.js";
42
- import { t as EvalResult } from "./evalResult-B8LbgWMA.js";
43
- import { t as formatDuration } from "./formatDuration-CGa0ZDQ8.js";
44
- import { n as shouldUseInkUI } from "./interactiveCheck-Dmm9vjpx.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-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-C6QXA9Pn.js");
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$1 = /^[a-f0-9]{64}$/i;
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$1.test(candidate.hash) && typeof candidate.mimeType === "string") return normalizeHash(candidate.hash);
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 completionMessage = writeToDatabase && shareableUrl ? `${chalk.green("✓")} ${completionType} complete: ${shareableUrl}` : writeToDatabase && activelySharing ? `${chalk.green("✓")} ${completionType} complete` : writeToDatabase ? `${chalk.green("✓")} ${completionType} complete (ID: ${chalk.cyan(evalId)})` : `${chalk.green("✓")} ${completionType} complete`;
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("Could not locate config file(s) to watch");
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.warn(`Unknown command: ${command.args[0]}. Did you mean -c ${command.args[0]}?`);
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 hash = req.params.hash;
4690
- if (!BLOB_HASH_REGEX.test(hash)) {
4691
- res.status(400).json({ error: "Invalid blob hash" });
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 db = await getDb();
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 = req.query.type;
4746
- const query = db.select({
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 db = await getDb();
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 } = req.body;
4930
+ const { name, type, config } = bodyResult.data;
4766
4931
  const id = crypto.randomUUID();
4767
- const [result] = await db.insert(configsTable).values({
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 db = await getDb();
4785
- const type = req.params.type;
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 configs = await db.select({
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 db = await getDb();
4802
- const type = req.params.type;
4803
- const id = req.params.id;
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 config = await db.select().from(configsTable).where(and(eq(configsTable.type, type), eq(configsTable.id, id))).limit(1);
4806
- logger_default.info(`Loaded config ${id} of type ${type}`);
4807
- if (!config.length) {
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
- res.json(config[0]);
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
- logger_default.debug(`Extracting goal for ${testCasesWithMetadata.length} tests from ${plugin.id}...`);
6547
- for (const testCase of testCasesWithMetadata) {
6548
- const promptVar = testCase.vars?.[injectVar];
6549
- const prompt = Array.isArray(promptVar) ? promptVar[0] : String(promptVar);
6550
- const policy = getPolicyText(testCase.metadata);
6551
- const extractedGoal = await extractGoalFromPrompt(prompt, purpose, plugin.id, policy);
6552
- testCase.metadata.goal = extractedGoal;
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
- logger_default.debug(`Extracting goal for ${testCasesWithMetadata.length} custom tests from ${plugin.id}...`);
6588
- for (const testCase of testCasesWithMetadata) {
6589
- const promptVar = testCase.vars?.[injectVar];
6590
- const prompt = Array.isArray(promptVar) ? promptVar[0] : String(promptVar);
6591
- const policy = getPolicyText(testCase.metadata);
6592
- const extractedGoal = await extractGoalFromPrompt(prompt, purpose, plugin.id, policy);
6593
- testCase.metadata.goal = extractedGoal;
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: { Query: EvalTableQuerySchema }
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 { evaluateOptions, ...testSuite } = req.body;
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 (result) => {
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 result.toEvaluateSummary();
7592
- job.evalId = result.id;
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 id = req.params.id;
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 id = req.params.id;
7633
- const { table, config } = req.body;
7634
- if (!id) {
7635
- res.status(400).json({ error: "Missing id" });
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
- if (error instanceof z.ZodError) res.status(400).json({ error: z.prettifyError(error) });
7664
- else {
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 id = req.params.id;
7673
- const queryResult = EvalTableQuerySchema.safeParse(req.query);
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 id = req.params.id;
7839
- const results = req.body;
7840
- if (!Array.isArray(results)) {
7841
- res.status(400).json({ error: "Results must be an array" });
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 { evaluationId, testIndex, prompt, variables } = req.body;
7860
- if (!evaluationId || !prompt) {
7861
- res.status(400).json({ error: "Missing required parameters" });
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 (!output && firstResult?.response?.raw) output = firstResult.response.raw;
7899
- res.json({
7900
- output: output || "",
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 id = req.params.id;
7911
- const gradingResult = req.body;
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 body = req.body;
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 { data: payload } = req.body;
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 id = req.params.id;
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 ids = req.body.ids;
7999
- if (!Array.isArray(ids)) {
8000
- res.status(400).json({ error: "Ids must be an array" });
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: req.params.id
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 = req.params.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 = req.params.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 } = req.body;
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
- logger_default.error(`Error checking path: ${error}`);
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 = {} } = req.body;
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
- originalError: error.message,
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
- safeRespond(500, {
9205
- error: errorMessage,
9706
+ logger_default.error("Model scan failed", {
9206
9707
  exitCode: code,
9207
9708
  stderr: stderr || void 0,
9208
- stdout: stdout || void 0,
9209
- ...errorDetails,
9210
- debug: {
9211
- command: "modelaudit",
9212
- args,
9213
- paths: resolvedPaths,
9214
- cwd: process.cwd()
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
- stderr: stderr || void 0,
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(`Failed to parse model scan output: ${parseError}`);
9241
- safeRespond(500, {
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
- suggestion: "The model scan may have produced invalid output. Check the raw output for error messages.",
9247
- debug: {
9248
- command: "modelaudit",
9249
- args,
9250
- paths: resolvedPaths,
9251
- cwd: process.cwd(),
9252
- exitCode: code
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 || null,
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(`Error processing model scan results: ${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
- logger_default.error(`Error in model scan endpoint: ${error}`);
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 = Math.min(Math.max(1, parseInt(req.query.limit) || 100), 100);
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
- logger_default.error(`Error fetching model audits: ${error}`);
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
- logger_default.error(`Error fetching latest model audit: ${error}`);
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(req.params.id);
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
- logger_default.error(`Error fetching model audit: ${error}`);
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(req.params.id);
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
- logger_default.error(`Error deleting model audit: ${error}`);
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: ProviderOptionsSchema
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
- let payload;
10967
- try {
10968
- payload = ProviderSchemas.Test.Request.parse(req.body);
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 = payload.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: payload.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 body = req.body;
11001
- let providerOptions;
11002
- try {
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
- invariant(providerOptions.id, "Provider ID (`id`) is required");
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.status(500).json({ error: `Discovery failed: ${errorMessage}` });
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 { requestExample, responseExample } = req.body;
11029
- if (!requestExample) {
11030
- res.status(400).json({ error: "Request example is required" });
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.status(500).json({
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 { provider: providerOptions, sessionConfig, mainInputVariable } = req.body;
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
- const errorMessage = error instanceof Error ? error.message : String(error);
11681
+ logger_default.error("[POST /providers/test-session] Error testing session", { error });
11170
11682
  res.status(500).json({
11171
11683
  success: false,
11172
- message: `Failed to test session: ${errorMessage}`,
11173
- error: errorMessage
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 = TestCaseGenerationSchema.safeParse(req.body);
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}: ${error}`);
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(`Error generating test case: ${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 } = req.body;
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: Number(delay || "0"),
11662
- maxConcurrency: normalizedMaxConcurrency,
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 { taskId } = req.params;
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
- task: taskId,
11742
- ...req.body
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 = req.params.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 = req.params.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: { Response: GetEmailStatusResponseSchema },
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
- if (error instanceof z.ZodError) res.status(400).json({ error: z.prettifyError(error) });
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 result = await checkEmailStatus({ validate: req.query.validate === "true" });
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
- if (error instanceof z.ZodError) res.status(400).json({ error: z.prettifyError(error) });
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
- if (error instanceof z.ZodError) res.status(400).json({ error: z.prettifyError(error) });
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 2.5 Pro, ...",
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-kFatbqG6.js"),
14587
- import("./ListApp-CYTAaS6A.js")
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-B4qKt437.js");
16907
- const evalRecord = await (await import("./eval-DgiEH2xe.js")).default.create(config, filteredTestSuite.prompts, { id: `mcp-eval-${Date.now()}` });
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
  }