promptfoo 0.121.4 → 0.121.7

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 (497) hide show
  1. package/dist/src/{ListApp-DQkFNqE9.js → ListApp-DLmM02JS.js} +1 -1
  2. package/dist/src/{accounts-DhMYUUbu.js → accounts-Ca7WIoPY.js} +12 -7
  3. package/dist/src/{accounts-F9d_5sMC.js → accounts-CjFnOPmb.js} +14 -9
  4. package/dist/src/{accounts-Dy17bs4D.cjs → accounts-CmWzeD2d.cjs} +16 -10
  5. package/dist/src/{accounts-DdJ2pHMI.js → accounts-DanM1wq_.js} +13 -8
  6. package/dist/src/{agentic-utils-qFlm6zes.js → agentic-utils-CJ0j3fBi.js} +3 -3
  7. package/dist/src/{agentic-utils-w68v6_Dz.js → agentic-utils-DDEGRV9v.js} +3 -3
  8. package/dist/src/{agentic-utils-BpX5b23w.cjs → agentic-utils-DvPWSUpb.cjs} +8 -7
  9. package/dist/src/{agentic-utils-P172hM8B.js → agentic-utils-TxUEMPYS.js} +2 -2
  10. package/dist/src/{agents-BahDpe5G.cjs → agents-B4sRuXg3.cjs} +7 -6
  11. package/dist/src/{agents-pQeBEXMm.js → agents-B8q7h_ek.js} +5 -5
  12. package/dist/src/{agents-CgaMXvLM.js → agents-CBgJvRkB.js} +21 -10
  13. package/dist/src/{agents-C-R_jfzI.js → agents-CYn2n3QP.js} +4 -4
  14. package/dist/src/{agents-8FDnTriG.js → agents-D-vDNFx4.js} +21 -10
  15. package/dist/src/{agents-aYPQLf8W.js → agents-LrHuQqr1.js} +20 -9
  16. package/dist/src/{agents-DJ35I3Nt.js → agents-QGg76OF-.js} +5 -5
  17. package/dist/src/{agents-D7-HGxUj.cjs → agents-eHZ9nlgA.cjs} +21 -10
  18. package/dist/src/{aimlapi-sgYnkE54.js → aimlapi-CJEbQ0o6.js} +7 -7
  19. package/dist/src/{aimlapi-BD6J9oKt.js → aimlapi-D5HXzZ0s.js} +6 -6
  20. package/dist/src/{aimlapi-qcK4OT55.cjs → aimlapi-T6HGNxNe.cjs} +7 -7
  21. package/dist/src/{aimlapi-BCq3MHeL.js → aimlapi-eYv3a_DK.js} +7 -7
  22. package/dist/src/app/app/tsconfig.app.tsbuildinfo +1 -1
  23. package/dist/src/app/assets/Report-BNHJKN35.js +1 -0
  24. package/dist/src/app/assets/index-BnT6P6sF.js +388 -0
  25. package/dist/src/app/assets/index-yhM8y1PP.css +1 -0
  26. package/dist/src/app/assets/{scroll-timeline-D9IT_e8Z.js → scroll-timeline-RpeTwOvs.js} +1 -1
  27. package/dist/src/app/assets/sync-5gq6fmG4.js +4 -0
  28. package/dist/src/app/assets/vendor-charts-BL9OMNU7.js +36 -0
  29. package/dist/src/app/assets/{vendor-markdown-Ch00wnNI.js → vendor-markdown-BYsQqn7Z.js} +10 -10
  30. package/dist/src/app/assets/{vendor-react-CVvmk1UB.js → vendor-react-CqWgVW6T.js} +2 -2
  31. package/dist/src/app/assets/{vendor-utils-BnEYbx2Q.js → vendor-utils-BHPO71pu.js} +1 -1
  32. package/dist/src/app/index.html +31 -6
  33. package/dist/src/{audio-COrn8rM6.js → audio-BqnRvcWG.js} +3 -3
  34. package/dist/src/{audio-DcVKoInv.js → audio-CPMtV1yR.js} +4 -4
  35. package/dist/src/{audio-B7izf48x.js → audio-DyiebVB3.js} +4 -4
  36. package/dist/src/{audio-BQtNuYBj.cjs → audio-FnxbEnSE.cjs} +4 -4
  37. package/dist/src/authoritativeMarkupInjection-BZIywVjG.js +74 -0
  38. package/dist/src/authoritativeMarkupInjection-DyAXAsSr.js +75 -0
  39. package/dist/src/authoritativeMarkupInjection-F2gBw0lN.cjs +74 -0
  40. package/dist/src/authoritativeMarkupInjection-QEQmFS83.js +74 -0
  41. package/dist/src/{base-PYJvBE1i.js → base-CKLo890h.js} +4 -3
  42. package/dist/src/{base-fZ9wgg50.js → base-Co80MMCi.js} +5 -4
  43. package/dist/src/{base-D-670DX8.cjs → base-DGJW48uz.cjs} +5 -4
  44. package/dist/src/{base-yrI1Yal4.js → base-E9I8zXjz.js} +5 -4
  45. package/dist/src/bestOfN-B3wNzjSB.js +137 -0
  46. package/dist/src/bestOfN-BBsO41z4.js +136 -0
  47. package/dist/src/bestOfN-CAwmg5UL.cjs +140 -0
  48. package/dist/src/bestOfN-_kTi8Bxe.js +136 -0
  49. package/dist/src/{blobs-D2FAd1Q5.cjs → blobs-B0977K1O.cjs} +7 -6
  50. package/dist/src/{blobs-BCZavS8s.js → blobs-CeFdPn_T.js} +3 -3
  51. package/dist/src/{blobs-BQWqnnvL.js → blobs-DODuTK-a.js} +3 -3
  52. package/dist/src/{blobs-C-F78Kfn.js → blobs-Dwef1Ao1.js} +2 -2
  53. package/dist/src/{cache-BIyPcp5v.cjs → cache-CPGUA4Yl.cjs} +135 -25
  54. package/dist/src/cache-Cf7b4pWE.js +3 -0
  55. package/dist/src/{cache-D5NZmMiT.js → cache-DIXbtkNO.js} +125 -10
  56. package/dist/src/{cache-mb7c8hbp.js → cache-DpPWrkTE.js} +128 -12
  57. package/dist/src/{cache-C4Xb-hNb.js → cache-roFAE0cI.js} +126 -11
  58. package/dist/src/{chat-I9izLm49.js → chat-CUCorGiL.js} +12 -12
  59. package/dist/src/{chat-BPXSW8Bv.cjs → chat-DG1wG4w0.cjs} +6 -6
  60. package/dist/src/{chat-BfPaS15_.js → chat-Dabu84Br.js} +12 -12
  61. package/dist/src/{chat-Dr3DUQ0D.js → chat-DqUFcWI0.js} +12 -12
  62. package/dist/src/{chat-CclRbxGf.cjs → chat-DxTDQ83C.cjs} +14 -13
  63. package/dist/src/{chat-MKxMnZJZ.js → chat-GmlolEwo.js} +4 -4
  64. package/dist/src/{chat-0bwXjVP0.js → chat-TP8Qifkh.js} +6 -6
  65. package/dist/src/{chat-mW0ORo8G.js → chat-iwaM5UTQ.js} +6 -6
  66. package/dist/src/{chatkit-zUIVoDos.js → chatkit-B6DWi70Q.js} +4 -4
  67. package/dist/src/{chatkit-BoWoSgXl.cjs → chatkit-BYveR48_.cjs} +6 -5
  68. package/dist/src/{chatkit-Cv6AhukM.js → chatkit-fARZwEfV.js} +3 -3
  69. package/dist/src/{chatkit-CJnHRRMM.js → chatkit-lb6FK02w.js} +4 -4
  70. package/dist/src/{claude-agent-sdk-Dtq_L-Sc.js → claude-agent-sdk-BQNp_y-F.js} +212 -67
  71. package/dist/src/{claude-agent-sdk-BQNuLaAK.js → claude-agent-sdk-D5Jl0SDh.js} +212 -67
  72. package/dist/src/{claude-agent-sdk-CPJo3dBQ.cjs → claude-agent-sdk-DH416NBD.cjs} +218 -72
  73. package/dist/src/{claude-agent-sdk-nfAIcxNf.js → claude-agent-sdk-x1XJ1-pU.js} +212 -67
  74. package/dist/src/{cloud-DQZ5sVjW.js → cloud-D3DiFqH6.js} +3 -3
  75. package/dist/src/cloud-p96PA4MH.js +3 -0
  76. package/dist/src/{cloudflare-ai-BIB567w6.js → cloudflare-ai-B6NVI3ax.js} +4 -4
  77. package/dist/src/{cloudflare-ai-Dl3N9OVD.cjs → cloudflare-ai-CEAW-xQa.cjs} +6 -6
  78. package/dist/src/{cloudflare-ai-DlKr0rY7.js → cloudflare-ai-RFSojyXG.js} +6 -6
  79. package/dist/src/{cloudflare-ai-DGLte7Py.js → cloudflare-ai-r4tbYmWU.js} +6 -6
  80. package/dist/src/{cloudflare-gateway-CiIZHU0Q.js → cloudflare-gateway-BCkLouto.js} +5 -5
  81. package/dist/src/{cloudflare-gateway-DI1HNP5F.js → cloudflare-gateway-BaZ4insB.js} +3 -3
  82. package/dist/src/{cloudflare-gateway-BDZrYydE.js → cloudflare-gateway-CF-Vb-2Z.js} +5 -5
  83. package/dist/src/{cloudflare-gateway-BYDp495F.cjs → cloudflare-gateway-TJMLBj6I.cjs} +5 -5
  84. package/dist/src/codex-app-server-B8KHEiF4.js +1915 -0
  85. package/dist/src/codex-app-server-CnrLBCeA.cjs +1921 -0
  86. package/dist/src/codex-app-server-DIXZ230V.js +1915 -0
  87. package/dist/src/codex-app-server-Dd22dC_N.js +1916 -0
  88. package/dist/src/{codex-sdk-CpqiOqDO.js → codex-sdk-B6Wah8Pa.js} +6 -6
  89. package/dist/src/codex-sdk-BGjVAk23.js +3 -0
  90. package/dist/src/{codex-sdk-C2_M2pl_.cjs → codex-sdk-CFF6gUyi.cjs} +18 -10
  91. package/dist/src/{codex-sdk-Rtky3M4I.js → codex-sdk-CmQABzV3.js} +6 -6
  92. package/dist/src/{codex-sdk-CErXn7qh.js → codex-sdk-D2d54RL8.js} +5 -5
  93. package/dist/src/{cometapi-CtJ-mS8R.js → cometapi-Bu9B8NUY.js} +8 -8
  94. package/dist/src/{cometapi-DT-jlVCB.js → cometapi-CtzNCHKu.js} +7 -7
  95. package/dist/src/{cometapi-UVOryo4W.cjs → cometapi-DHCDlQUI.cjs} +8 -8
  96. package/dist/src/{cometapi-BUlt_ELa.js → cometapi-OBILPLlu.js} +8 -8
  97. package/dist/src/{completion-HUe8wDhZ.js → completion-CO2e1_62.js} +6 -6
  98. package/dist/src/{completion-BozdoXba.cjs → completion-CSYfl2cd.cjs} +6 -6
  99. package/dist/src/{completion-x0a_c2y1.js → completion-DZNxcyfG.js} +6 -6
  100. package/dist/src/{completion-Dnxn7E-j.js → completion-sNvCLTAP.js} +5 -5
  101. package/dist/src/constants-BjJV0cRr.js +6 -0
  102. package/dist/src/constants-DH5XYLKZ.js +7 -0
  103. package/dist/src/constants-DZGEFLsu.js +6 -0
  104. package/dist/src/constants-a2kYssQk.cjs +11 -0
  105. package/dist/src/{createHash-4gFQpDDv.js → createHash-BtbSX3mj.js} +1 -1
  106. package/dist/src/{createHash-CwDVU5xr.js → createHash-CGVzWdjj.js} +1 -1
  107. package/dist/src/{createHash-B7KvgoOD.cjs → createHash-CSiqnK5P.cjs} +2 -2
  108. package/dist/src/{createHash-ChI45QR1.js → createHash-CgRvs4Fn.js} +1 -1
  109. package/dist/src/crescendo-BXEJK_bi.cjs +704 -0
  110. package/dist/src/crescendo-CU_Y2i-m.js +702 -0
  111. package/dist/src/crescendo-J1Xx4_zb.js +703 -0
  112. package/dist/src/crescendo-QiaSLW0d.js +701 -0
  113. package/dist/src/custom-BJfP00Bh.js +619 -0
  114. package/dist/src/custom-CZVn-1-r.js +620 -0
  115. package/dist/src/custom-Cqia7M0D.cjs +621 -0
  116. package/dist/src/custom-notggYVl.js +618 -0
  117. package/dist/src/{docker-DCgsveLD.js → docker-4D1eL6Gq.js} +6 -6
  118. package/dist/src/{docker-ClnmCf1Z.js → docker-BBv1WUDu.js} +5 -5
  119. package/dist/src/{docker-DS4_Osau.cjs → docker-D06JUoe2.cjs} +6 -6
  120. package/dist/src/{docker-CQmlA2NU.js → docker-DdJQBxK9.js} +6 -6
  121. package/dist/src/{embedding-D3xTseo7.js → embedding--UZVe4_7.js} +6 -6
  122. package/dist/src/{embedding-I45KG3o7.cjs → embedding-BbrwopfX.cjs} +6 -6
  123. package/dist/src/{embedding-nFbumxcv.js → embedding-Bi3rxrZF.js} +5 -5
  124. package/dist/src/{embedding-DD9wa3ae.js → embedding-C251p1-8.js} +6 -6
  125. package/dist/src/{errors-Cw810C93.js → errors-9PcUL8BC.js} +1 -1
  126. package/dist/src/{esm-Dh4dOLlt.js → esm-B6whoAcf.js} +2 -2
  127. package/dist/src/{esm-CtEPLdAj.cjs → esm-BIKakvNa.cjs} +8 -7
  128. package/dist/src/{esm-C7PnfdF8.js → esm-BTK1W7lG.js} +1 -1
  129. package/dist/src/{esm-tVgYPY-f.js → esm-Bexx2PFc.js} +2 -2
  130. package/dist/src/{eval-u4UVafl6.js → eval-0VRANImH.js} +21 -21
  131. package/dist/src/{eval-CzJFfFO9.js → eval-DscR5iOM.js} +1 -1
  132. package/dist/src/{evalResult-Bgm9ZH31.js → evalResult-2RRJvFyB.js} +41 -16
  133. package/dist/src/{evalResult-KZqXl4XP.cjs → evalResult-CvtS8h8u.cjs} +51 -15
  134. package/dist/src/evalResult-DqzsS6_W.js +3 -0
  135. package/dist/src/{evalResult-D3hVYFis.js → evalResult-eUkJv9Ko.js} +40 -15
  136. package/dist/src/evaluator-DNdJF1Gv.js +3 -0
  137. package/dist/src/{evaluator-IvuDYSvQ.js → evaluator-DRoiYB2q.js} +1060 -187
  138. package/dist/src/evaluatorHelpers-BsYP_muT.js +511 -0
  139. package/dist/src/evaluatorHelpers-CRqTvSux.cjs +537 -0
  140. package/dist/src/evaluatorHelpers-DuqFFfq7.js +510 -0
  141. package/dist/src/{extractor-CAfTSraf.js → extractor-BR7XAzAL.js} +6 -6
  142. package/dist/src/{extractor-WVPOrH43.cjs → extractor-BdxEtt3J.cjs} +6 -6
  143. package/dist/src/{extractor-DNSeBVOJ.js → extractor-CIW3iN-b.js} +6 -6
  144. package/dist/src/{extractor-Dk6bRWkv.js → extractor-CxRtnaHl.js} +5 -5
  145. package/dist/src/{fetch-B0Z3Oe4k.js → fetch-BufrQtvR.js} +93 -40
  146. package/dist/src/{fetch-BEWnXrrG.js → fetch-DXUnXkVU.js} +89 -40
  147. package/dist/src/{fetch-CJU5ELPa.cjs → fetch-Dw4XZHjj.cjs} +330 -270
  148. package/dist/src/{fetch-Di00EQrc.js → fetch-It34O8Ur.js} +305 -252
  149. package/dist/src/fetch-_YgGd2qv.js +3 -0
  150. package/dist/src/{fileExtensions-bYh77CN8.cjs → fileExtensions-BhdwzYaD.cjs} +24 -1
  151. package/dist/src/{fileExtensions-DnqA1y9x.js → fileExtensions-CXRfY3Ss.js} +12 -2
  152. package/dist/src/{fileExtensions-AWa2ZML4.js → fileExtensions-D4GCJ67J.js} +12 -2
  153. package/dist/src/{formatDuration-DZzPsexs.js → formatDuration-CMVNrYvE.js} +1 -1
  154. package/dist/src/{genaiTracer-yRuxj9-L.cjs → genaiTracer-14nugQQx.cjs} +14 -2
  155. package/dist/src/{genaiTracer-DWdZ28hY.js → genaiTracer-BPVvltoW.js} +2 -2
  156. package/dist/src/{genaiTracer-XnrcgDCe.js → genaiTracer-D18lYzhB.js} +2 -2
  157. package/dist/src/{genaiTracer-COYDi-tC.js → genaiTracer-jJKYsnjc.js} +2 -2
  158. package/dist/src/goat-Ckd3q3AY.js +467 -0
  159. package/dist/src/goat-Qgurm-NP.js +466 -0
  160. package/dist/src/goat-ghadEDdy.js +465 -0
  161. package/dist/src/goat-una6pZGP.cjs +469 -0
  162. package/dist/src/graders-BDT7dif6.js +3 -0
  163. package/dist/src/{graders-eIHhRqoC.js → graders-BGP99PdK.js} +2416 -2224
  164. package/dist/src/{graders-Zy3x0zqX.js → graders-BX0f2tvS.js} +2423 -2226
  165. package/dist/src/{graders-pvbReLLn.js → graders-C0nXU_ZP.js} +1806 -1609
  166. package/dist/src/{graders--zknU_uk.cjs → graders-ClrU2fnd.cjs} +2219 -1949
  167. package/dist/src/hydra-BSNZZm2M.js +543 -0
  168. package/dist/src/hydra-BxdG4nkg.js +541 -0
  169. package/dist/src/hydra-DE4xWwyc.js +542 -0
  170. package/dist/src/hydra-DrJttnvw.cjs +542 -0
  171. package/dist/src/image-B4oBtu6J.js +443 -0
  172. package/dist/src/{image-dnoUgPrC.js → image-BN-hjLL9.js} +4 -4
  173. package/dist/src/{image-9302QVqR.js → image-B_fPIwdg.js} +3 -3
  174. package/dist/src/image-BvUAW344.js +442 -0
  175. package/dist/src/image-Cvjwx1uY.js +442 -0
  176. package/dist/src/{image-De2FBmYV.cjs → image-DfVCGPbI.cjs} +4 -4
  177. package/dist/src/{image-u7-rKnYU.js → image-QzmydkiG.js} +4 -4
  178. package/dist/src/image-X0oY4350.cjs +465 -0
  179. package/dist/src/index.cjs +1689 -558
  180. package/dist/src/index.d.cts +3270 -1624
  181. package/dist/src/index.d.ts +3270 -1624
  182. package/dist/src/index.js +1553 -438
  183. package/dist/src/indirectWebPwn-02ZIghCS.js +259 -0
  184. package/dist/src/indirectWebPwn-BJ22AbQa.cjs +397 -0
  185. package/dist/src/indirectWebPwn-CbjUG0rh.js +385 -0
  186. package/dist/src/indirectWebPwn-CfQJt3gk.cjs +260 -0
  187. package/dist/src/indirectWebPwn-DBQhOjoD.js +260 -0
  188. package/dist/src/indirectWebPwn-OsXnKejv.js +259 -0
  189. package/dist/src/indirectWebPwn-tNx9OZ35.js +385 -0
  190. package/dist/src/indirectWebPwn-uyWdHx04.js +386 -0
  191. package/dist/src/inputVariables-B0qUChbV.js +467 -0
  192. package/dist/src/inputVariables-DUGMb9Ka.js +464 -0
  193. package/dist/src/inputVariables-DXFdi7AI.js +468 -0
  194. package/dist/src/inputVariables-Dq9W-Z3a.cjs +475 -0
  195. package/dist/src/{interactiveCheck-CLERUB0c.js → interactiveCheck-C4QlIuoR.js} +2 -2
  196. package/dist/src/{invariant-BtWWVVhl.js → invariant-B2Rf6avk.js} +1 -1
  197. package/dist/src/{invariant-vgHWClmd.js → invariant-DIYf9sP1.js} +1 -1
  198. package/dist/src/{invariant-kfQ8Bu82.cjs → invariant-QtnLD03y.cjs} +1 -1
  199. package/dist/src/iterative-CpU6i2As.js +490 -0
  200. package/dist/src/iterative-DJQEQpG3.js +491 -0
  201. package/dist/src/iterative-DQBuWM-j.cjs +493 -0
  202. package/dist/src/iterative-FTS4Bz67.js +492 -0
  203. package/dist/src/iterativeImage-BUABMVOA.js +413 -0
  204. package/dist/src/iterativeImage-ByFWkxax.cjs +415 -0
  205. package/dist/src/iterativeImage-BzUapOUi.js +414 -0
  206. package/dist/src/iterativeImage-Doz8mgxF.js +413 -0
  207. package/dist/src/iterativeMeta-B3YiAOc8.js +386 -0
  208. package/dist/src/iterativeMeta-C7APE_P1.js +385 -0
  209. package/dist/src/iterativeMeta-CSS8M6Ds.cjs +385 -0
  210. package/dist/src/iterativeMeta-DgoQ7bLh.js +384 -0
  211. package/dist/src/iterativeTree-B5zxBBSW.js +769 -0
  212. package/dist/src/iterativeTree-CNyIk0Yn.js +768 -0
  213. package/dist/src/iterativeTree-CPMF10ve.cjs +771 -0
  214. package/dist/src/iterativeTree-DvZ7GBwt.js +770 -0
  215. package/dist/src/{knowledgeBase-Dgc7CBWF.js → knowledgeBase-BadkINlJ.js} +24 -10
  216. package/dist/src/{knowledgeBase-RhFPGWDc.js → knowledgeBase-Bi_8sV-H.js} +25 -11
  217. package/dist/src/{knowledgeBase-lm9RXSAm.js → knowledgeBase-CkMljjdg.js} +25 -11
  218. package/dist/src/{knowledgeBase-Bpoe_nLu.cjs → knowledgeBase-DUh34xba.cjs} +25 -11
  219. package/dist/src/{litellm-DRjpcSa7.js → litellm-BKBo0jpC.js} +5 -5
  220. package/dist/src/{litellm-C2kqjxqp.js → litellm-BXyn5kZK.js} +5 -5
  221. package/dist/src/{litellm-p37R1dzQ.js → litellm-CNcfbCfa.js} +4 -4
  222. package/dist/src/{litellm-CoyI4IAl.cjs → litellm-CtAr7bKG.cjs} +5 -5
  223. package/dist/src/{logger-DksKw1Qc.js → logger-BbY6ypFL.js} +2 -2
  224. package/dist/src/{logger-B88EkIn6.js → logger-KD8JjCRJ.js} +2 -2
  225. package/dist/src/{logger-COuQb2xB.cjs → logger-cfNpzI4o.cjs} +13 -55
  226. package/dist/src/{luma-ray-KgTCXrZC.js → luma-ray-BMX1iEB6.js} +5 -5
  227. package/dist/src/{luma-ray-B863CmuZ.js → luma-ray-CR5TSpp4.js} +5 -5
  228. package/dist/src/{luma-ray-BxVKaW2a.cjs → luma-ray-D3FUc2K3.cjs} +9 -8
  229. package/dist/src/{luma-ray-BTTLtqQ8.js → luma-ray-OEMmS1RB.js} +6 -6
  230. package/dist/src/main.js +909 -369
  231. package/dist/src/memoryPoisoning-CM83NWYl.js +107 -0
  232. package/dist/src/memoryPoisoning-D8h9gXJF.js +106 -0
  233. package/dist/src/memoryPoisoning-Dp-btinn.cjs +106 -0
  234. package/dist/src/memoryPoisoning-cLuCoTuJ.js +106 -0
  235. package/dist/src/{messages-BTQz42fn.js → messages-BabO-cX8.js} +273 -17
  236. package/dist/src/{messages-811uVVW5.cjs → messages-DBPir0TQ.cjs} +278 -18
  237. package/dist/src/{messages-zWbkLLHz.js → messages-DGUlSNU7.js} +273 -17
  238. package/dist/src/{messages-MYTQ2TWp.js → messages-vsE_-Lv0.js} +273 -17
  239. package/dist/src/{meteor-DHdzY1Ss.js → meteor--TZYICTI.js} +2 -2
  240. package/dist/src/{meteor-Co1VQ1u5.cjs → meteor-CR226f7Z.cjs} +2 -2
  241. package/dist/src/{meteor-CU5UAE-H.js → meteor-Cl_yd7rJ.js} +2 -2
  242. package/dist/src/{meteor-DuAFv6gF.js → meteor-Dce-_zGQ.js} +1 -1
  243. package/dist/src/mischievousUser-0l8GD7Dp.js +46 -0
  244. package/dist/src/mischievousUser-BUOP9W5r.js +46 -0
  245. package/dist/src/mischievousUser-frFYKxu6.js +47 -0
  246. package/dist/src/mischievousUser-olGgHIVR.cjs +46 -0
  247. package/dist/src/{modelslab-Dk1JAtVo.cjs → modelslab-CNV5bMSk.cjs} +7 -7
  248. package/dist/src/{modelslab-D0erNWKe.js → modelslab-Cogmu4mG.js} +6 -6
  249. package/dist/src/{modelslab-DIq-6y7x.js → modelslab-Dzst7VTU.js} +6 -6
  250. package/dist/src/{modelslab-wu9yi5GE.js → modelslab-EyDczZ5A.js} +7 -7
  251. package/dist/src/{nova-reel-CCFRfeRb.js → nova-reel-BGPNBOMS.js} +6 -6
  252. package/dist/src/{nova-reel-DQrm74ng.js → nova-reel-B_5NKFu1.js} +5 -5
  253. package/dist/src/{nova-reel-gr11WG7f.js → nova-reel-C4eUJGse.js} +5 -5
  254. package/dist/src/{nova-reel-CrLXVKQf.cjs → nova-reel-CjJRxI1X.cjs} +9 -8
  255. package/dist/src/{nova-sonic-BYdp-QLs.js → nova-sonic-BNGmgfFz.js} +4 -4
  256. package/dist/src/{nova-sonic-TDgrlTk7.js → nova-sonic-ChPlh5na.js} +4 -4
  257. package/dist/src/{nova-sonic-B_ZXcUJB.js → nova-sonic-CrV0iaY_.js} +3 -3
  258. package/dist/src/{nova-sonic-i5tUvXKn.cjs → nova-sonic-DuOG9Aun.cjs} +5 -4
  259. package/dist/src/{openai-DhVEmgeZ.js → openai-BMHD2Huo.js} +2 -2
  260. package/dist/src/{openai-URNyItar.cjs → openai-C3uXv8wS.cjs} +2 -2
  261. package/dist/src/{openai-Qsvz25mV.js → openai-CJrsh9n4.js} +2 -2
  262. package/dist/src/{openai-iYtrXzOX.js → openai-zgwBb4Ff.js} +1 -1
  263. package/dist/src/{openclaw-CnQ363Wi.js → openclaw-BIHlu_36.js} +10 -8
  264. package/dist/src/{openclaw-CwzlQSQX.js → openclaw-CF7fMido.js} +9 -7
  265. package/dist/src/{openclaw-wX9rtfke.cjs → openclaw-Dphc01BY.cjs} +18 -15
  266. package/dist/src/{openclaw-CLWrW03k.js → openclaw-zIJAsz3P.js} +10 -8
  267. package/dist/src/{opencode-sdk-BUu5Nevv.js → opencode-sdk-B3vlPLsp.js} +40 -5
  268. package/dist/src/{opencode-sdk-BxD8vXp_.js → opencode-sdk-D05JSgMQ.js} +40 -5
  269. package/dist/src/{opencode-sdk-BZ2idgYA.cjs → opencode-sdk-DoY6GbWw.cjs} +46 -10
  270. package/dist/src/{opencode-sdk-GI2KaAXq.js → opencode-sdk-sRKYHGoI.js} +39 -4
  271. package/dist/src/{otlpReceiver-BntK801g.js → otlpReceiver--gTpSagc.js} +120 -4
  272. package/dist/src/{otlpReceiver-DmVulbhC.js → otlpReceiver-B2eaKC8C.js} +120 -4
  273. package/dist/src/{otlpReceiver-B2z58l4e.js → otlpReceiver-BXjcRqAM.js} +119 -3
  274. package/dist/src/{otlpReceiver-BfcVq2Nq.cjs → otlpReceiver-CvJdBGSc.cjs} +125 -7
  275. package/dist/src/packageParser--MWTSrPW.js +36 -0
  276. package/dist/src/packageParser-CgE-ziRo.js +35 -0
  277. package/dist/src/packageParser-QoCS1FMl.cjs +54 -0
  278. package/dist/src/packageParser-hwwSGnAZ.js +35 -0
  279. package/dist/src/processShim-BBxt7LKO.js +95 -0
  280. package/dist/src/processShim-BcGzU8fY.js +94 -0
  281. package/dist/src/processShim-C_z3aRvF.js +94 -0
  282. package/dist/src/processShim-DSY9BV2T.cjs +98 -0
  283. package/dist/src/promptLength-0qIHyhA5.js +71 -0
  284. package/dist/src/promptLength-4X-Wd8PG.js +72 -0
  285. package/dist/src/promptLength-B9nZEfO6.js +71 -0
  286. package/dist/src/promptLength-BbBbDHNj.cjs +94 -0
  287. package/dist/src/promptfoo-BDrfT30-.js +180 -0
  288. package/dist/src/promptfoo-Cm4hiy1Y.js +180 -0
  289. package/dist/src/promptfoo-Rjp-MeBb.js +181 -0
  290. package/dist/src/promptfoo-b-baRMj-.cjs +205 -0
  291. package/dist/src/prompts-BYMtqPCw.js +259 -0
  292. package/dist/src/prompts-C-bqE1Yp.js +260 -0
  293. package/dist/src/prompts-Cp_Qx5Ml.js +270 -0
  294. package/dist/src/prompts-DHhQsANy.js +259 -0
  295. package/dist/src/prompts-D_QpZ2Dm.js +271 -0
  296. package/dist/src/prompts-hNvWBD3z.cjs +284 -0
  297. package/dist/src/prompts-huDVH2CI.js +270 -0
  298. package/dist/src/prompts-p78Hul5i.cjs +289 -0
  299. package/dist/src/{providerRegistry-CPQ_CmVO.js → providerRegistry-1gB5vtzQ.js} +2 -2
  300. package/dist/src/{providerRegistry-CQMdTmHP.cjs → providerRegistry-CZO_w7ue.cjs} +2 -2
  301. package/dist/src/{providerRegistry-Bvh8mv85.js → providerRegistry-DHcFiVWX.js} +1 -1
  302. package/dist/src/{providerRegistry-CWoPjKFZ.js → providerRegistry-ReCd0sFa.js} +2 -2
  303. package/dist/src/{providers-BV_KMZje.js → providers-B9KzWxAX.js} +10558 -21587
  304. package/dist/src/{providers-DruaQfwu.js → providers-BCCz6_IX.js} +1228 -12196
  305. package/dist/src/{providers-1eKkXBKp.cjs → providers-BDVVIQM6.cjs} +10649 -21843
  306. package/dist/src/{providers-iUt5fbAN.js → providers-BYAn82cf.js} +1 -1
  307. package/dist/src/{providers-Domz_llv.js → providers-DVYRZP4E.js} +10589 -21570
  308. package/dist/src/{pythonUtils-Cldx7huE.js → pythonUtils-CLCgQ9tt.js} +3 -3
  309. package/dist/src/{pythonUtils-CnndUbW-.js → pythonUtils-CgYxeSmO.js} +3 -3
  310. package/dist/src/{pythonUtils-tAJvvpS-.cjs → pythonUtils-Cokhluq3.cjs} +8 -7
  311. package/dist/src/{pythonUtils-C2UQ30Rz.js → pythonUtils-D0BYebvX.js} +3 -3
  312. package/dist/src/{quiverai-DFotyafY.cjs → quiverai-BAp6iTZD.cjs} +4 -4
  313. package/dist/src/{quiverai-aPPvXOgn.js → quiverai-BvIhI_0l.js} +4 -4
  314. package/dist/src/{quiverai-DR0SnIQV.js → quiverai-CdTWPe-A.js} +3 -3
  315. package/dist/src/{quiverai-CtWi6x_g.js → quiverai-Cv7rJKDz.js} +4 -4
  316. package/dist/src/registry-BUJrgjwv.js +124 -0
  317. package/dist/src/registry-DXm1t_x0.js +125 -0
  318. package/dist/src/registry-Dp5EqoXc.js +124 -0
  319. package/dist/src/registry-KCVF1CFC.cjs +124 -0
  320. package/dist/src/{server-D6Il2Sob.js → remoteGeneration-B1_XsKXU.js} +16 -108
  321. package/dist/src/{server-BSB45Nt9.js → remoteGeneration-COpWcmWd.js} +15 -146
  322. package/dist/src/{server-Dx2TyCH2.cjs → remoteGeneration-DS9N3pgB.cjs} +30 -119
  323. package/dist/src/remoteGeneration-DsaSwmG2.js +217 -0
  324. package/dist/src/render-BNTrbmBw.cjs +384 -0
  325. package/dist/src/render-CSP99NLm.js +348 -0
  326. package/dist/src/render-DFfDeYUK.js +347 -0
  327. package/dist/src/{render-CgVDrJmM.js → render-DznWrxGO.js} +2 -2
  328. package/dist/src/render-_6ur1fhE.js +347 -0
  329. package/dist/src/resourceAttributes-D1jP3kL5.js +17 -0
  330. package/dist/src/resourceAttributes-DQbBB--2.js +16 -0
  331. package/dist/src/resourceAttributes-ephgOvdR.cjs +27 -0
  332. package/dist/src/resourceAttributes-v6-I67fn.js +16 -0
  333. package/dist/src/{responses-Bi9vBuW_.cjs → responses-1UFFF9N_.cjs} +51 -16
  334. package/dist/src/{responses-DL9m8CyY.js → responses-B3W2JvOQ.js} +49 -15
  335. package/dist/src/{responses--OsX2aYW.js → responses-B6ktc3Ra.js} +49 -15
  336. package/dist/src/{responses-C-flexAY.js → responses-URRzV8qE.js} +49 -15
  337. package/dist/src/rolldown-runtime-D_mwlA32.cjs +43 -0
  338. package/dist/src/rubyUtils-BYVlQ94c.js +3 -0
  339. package/dist/src/{rubyUtils-DsGrTx8R.js → rubyUtils-CXlFM2rR.js} +3 -3
  340. package/dist/src/{rubyUtils-DVLeA2jg.js → rubyUtils-CnlW8AYb.js} +3 -3
  341. package/dist/src/{rubyUtils-B6eljPuh.cjs → rubyUtils-CqUWBZAt.cjs} +18 -27
  342. package/dist/src/{rubyUtils-CYSQEG4a.js → rubyUtils-DdGojpfv.js} +3 -3
  343. package/dist/src/runtimeTransform-BJOpL9Yc.js +142 -0
  344. package/dist/src/runtimeTransform-Dgh_D7DU.js +143 -0
  345. package/dist/src/runtimeTransform-DigbjU1r.js +142 -0
  346. package/dist/src/runtimeTransform-ON3YYILw.cjs +147 -0
  347. package/dist/src/{sagemaker-BVkaG2-l.js → sagemaker-CujrzP1a.js} +62 -51
  348. package/dist/src/{sagemaker-XnfhheQv.cjs → sagemaker-DzffAqo_.cjs} +65 -53
  349. package/dist/src/{sagemaker-D67yzMzs.js → sagemaker-vhtSV7JI.js} +62 -51
  350. package/dist/src/{sagemaker-BveBvuxm.js → sagemaker-yr1QKeBs.js} +61 -50
  351. package/dist/src/{scanner-1DqWi1Ej.js → scanner-DS0109SS.js} +7 -7
  352. package/dist/src/server/index.js +5105 -605
  353. package/dist/src/server-B8rqV126.cjs +126 -0
  354. package/dist/src/server-BaLytskk.js +3 -0
  355. package/dist/src/server-CMJD10J4.js +107 -0
  356. package/dist/src/server-Ddp8GNMp.js +146 -0
  357. package/dist/src/server-DhMHosWj.js +182 -0
  358. package/dist/src/shared-7pmVZLNO.js +1334 -0
  359. package/dist/src/shared-9WHQ1oNE.js +1335 -0
  360. package/dist/src/{fileExtensions-BArZuxsI.js → shared-BoG7qLMv.js} +12 -2
  361. package/dist/src/shared-D6IjElRI.js +1334 -0
  362. package/dist/src/shared-WkgnDkcg.cjs +1436 -0
  363. package/dist/src/{signal-CE5G3a7x.js → signal-CSurUUyV.js} +3 -3
  364. package/dist/src/simulatedUser-C9aQObBI.js +222 -0
  365. package/dist/src/simulatedUser-Cu601Dd4.cjs +227 -0
  366. package/dist/src/simulatedUser-U_qAHnuB.js +222 -0
  367. package/dist/src/simulatedUser-p3tACcmw.js +223 -0
  368. package/dist/src/{slack-DDUe-5MC.js → slack-Bapo-7_8.js} +2 -2
  369. package/dist/src/{slack-1Rhq0EoV.cjs → slack-DMC1QVEg.cjs} +3 -2
  370. package/dist/src/{slack-D5Wpy8LM.js → slack-DTEFhrMn.js} +2 -2
  371. package/dist/src/{slack-acRb0IqQ.js → slack-k-_CP84Q.js} +1 -1
  372. package/dist/src/storage-BU4qcnOb.js +875 -0
  373. package/dist/src/storage-CA-v9V2v.cjs +911 -0
  374. package/dist/src/storage-CD-GWAdx.js +822 -0
  375. package/dist/src/storage-QdU-SmvD.js +834 -0
  376. package/dist/src/{store-DAAyxcy6.cjs → store-B2NDDooM.cjs} +60 -24
  377. package/dist/src/{store-CYEy5J2D.js → store-DKd5592Q.js} +51 -20
  378. package/dist/src/{store-M0b1WfYb.js → store-HpopRVzl.js} +50 -19
  379. package/dist/src/store-IbiRIF3k.js +3 -0
  380. package/dist/src/strategies-7CS3Alao.cjs +2360 -0
  381. package/dist/src/strategies-CiSeroPH.js +2331 -0
  382. package/dist/src/strategies-DRJjGTIY.js +2333 -0
  383. package/dist/src/{tables-DQ4WU5tX.js → tables-CRSXQ2Ke.js} +2 -2
  384. package/dist/src/{tables-CsWou1Bx.js → tables-CxjU7bBd.js} +3 -3
  385. package/dist/src/{tables-DUfh1F7Z.cjs → tables-DBIJU0WE.cjs} +6 -5
  386. package/dist/src/{tables-C4CH3zRr.js → tables-DafUHOeh.js} +3 -3
  387. package/dist/src/{telemetry-CQPez_Jp.js → telemetry-00ezXr_t.js} +5 -4
  388. package/dist/src/telemetry-ByPqDcKC.js +3 -0
  389. package/dist/src/{telemetry-Dsw_faFj.cjs → telemetry-CJ7FnCsc.cjs} +18 -11
  390. package/dist/src/{telemetry-dbaJ0E98.js → telemetry-DmXYcJNV.js} +5 -4
  391. package/dist/src/{telemetry-Dvqxv3YC.js → telemetry-DwX9XUN5.js} +4 -3
  392. package/dist/src/{text-KvuD2Iko.js → text-Db-Wt2u2.js} +1 -1
  393. package/dist/src/{text-DHxdyQqT.js → text-DwYK5EBn.js} +1 -1
  394. package/dist/src/{text-BVi-cLPJ.cjs → text-nywWsRBM.cjs} +1 -1
  395. package/dist/src/{tokenUsageUtils-C-bmyHoE.js → tokenUsageUtils-BjVkdk18.js} +1 -1
  396. package/dist/src/{tokenUsageUtils-CXrvO-wA.js → tokenUsageUtils-CDet74yk.js} +1 -1
  397. package/dist/src/tokenUsageUtils-CmnQ0G2m.js +142 -0
  398. package/dist/src/{tokenUsageUtils-Bb7DkZPz.cjs → tokenUsageUtils-_B-P8IAi.cjs} +1 -1
  399. package/dist/src/toolAttributes-BAjwcBf0.cjs +103 -0
  400. package/dist/src/toolAttributes-COVgDrBG.js +87 -0
  401. package/dist/src/toolAttributes-DJ9ZEKXD.js +86 -0
  402. package/dist/src/tracingOptions-BnwKCkSB.js +221 -0
  403. package/dist/src/tracingOptions-Chi74lOD.js +219 -0
  404. package/dist/src/tracingOptions-DrbSFaKy.cjs +249 -0
  405. package/dist/src/tracingOptions-ji2OuXbT.js +220 -0
  406. package/dist/src/{transcription-DuWDupG7.js → transcription-B8uIgCYX.js} +5 -5
  407. package/dist/src/{transcription-CJspiD2c.js → transcription-CfU5loSq.js} +6 -6
  408. package/dist/src/{transcription-V2HaAmy2.js → transcription-Dkd22_4K.js} +6 -6
  409. package/dist/src/{transcription-BvjmiYB1.cjs → transcription-mzuf18Mq.cjs} +9 -8
  410. package/dist/src/{transform-lQrDE1BQ.js → transform-BIMynQsA.js} +9 -9
  411. package/dist/src/transform-BnSTnFlp.js +187 -0
  412. package/dist/src/transform-BnSXWmU_2.cjs +221 -0
  413. package/dist/src/transform-CGt7Kt3y2.js +186 -0
  414. package/dist/src/transform-CrPGTsij.js +186 -0
  415. package/dist/src/{transform-CTeuTR3S.cjs → transform-DhNkAUs8.cjs} +13 -12
  416. package/dist/src/{transform-CG0ehZNG.js → transform-DmvYBRll.js} +9 -9
  417. package/dist/src/{transform-zDhMmzwX.js → transform-EtD4jAWi.js} +9 -9
  418. package/dist/src/{transformersAvailability-CcHusyhw.js → transformersAvailability-0ThtPved.js} +1 -1
  419. package/dist/src/transformersAvailability-BYydDE5U.js +35 -0
  420. package/dist/src/{transformersAvailability-DLlROWhg.js → transformersAvailability-BvyU9vDD.js} +1 -1
  421. package/dist/src/{transformersAvailability-Cju9mHgR.cjs → transformersAvailability-BytPvKUW.cjs} +1 -1
  422. package/dist/src/{types-Dm9JM6Vb.js → types-BFevViUY.js} +115 -19
  423. package/dist/src/{types-Bgh5SOn6.js → types-BJQBBPTP.js} +115 -19
  424. package/dist/src/{types-CeaeaZdP.cjs → types-CxJvaY2S.cjs} +357 -172
  425. package/dist/src/{types-BGQDAP8i.js → types-D6glLbdF.js} +271 -170
  426. package/dist/src/{util-BYvQUPp7.js → util--WMgw7wM.js} +28 -8
  427. package/dist/src/{util-C9J8ahRn.js → util-5WnCSb0h.js} +72 -48
  428. package/dist/src/{util-CN3SrLT4.cjs → util-BSIuSLVK.cjs} +74 -49
  429. package/dist/src/{util-C8e5uydV.js → util-Bx677_k2.js} +154 -147
  430. package/dist/src/util-CN8om2rz.cjs +386 -0
  431. package/dist/src/{util-DDs-7g6-.js → util-CoQWM76y.js} +28 -8
  432. package/dist/src/util-DNl96nNs.js +327 -0
  433. package/dist/src/{util-DxWpWjhc.js → util-DURocbYR.js} +667 -507
  434. package/dist/src/util-Df8YMvS1.js +327 -0
  435. package/dist/src/{util-DvU2Pw8c.js → util-DiQ3QvBB.js} +28 -8
  436. package/dist/src/{util-oGMLA7vc.js → util-I-Rf-KaD.js} +862 -577
  437. package/dist/src/{util-olYL5C6N.cjs → util-IYzs5Y04.cjs} +33 -7
  438. package/dist/src/{util-D9TisOyk.js → util-LKTmNsMQ.js} +71 -47
  439. package/dist/src/{util-Bxn8emtE.cjs → util-SPsvFONY.cjs} +738 -582
  440. package/dist/src/{util-D3q0WQ-0.js → util-efByNxcr.js} +72 -48
  441. package/dist/src/util-kDURhgJW.js +328 -0
  442. package/dist/src/{utils-DJfvjyMj.js → utils-B0lzitHZ.js} +3 -3
  443. package/dist/src/{utils-BLJKfv0y.js → utils-BFOh20Gb.js} +3 -3
  444. package/dist/src/{utils-hXtCYanr.js → utils-BGY69tk_.js} +2 -2
  445. package/dist/src/{utils-B05gLxER.cjs → utils-Ve6kuJsa.cjs} +3 -3
  446. package/dist/src/version-BK20a4sw.js +16 -0
  447. package/dist/src/version-BWCSaByA.cjs +27 -0
  448. package/dist/src/version-eRkNuGv8.js +17 -0
  449. package/dist/src/version-lpHV_53E.js +16 -0
  450. package/dist/tsconfig.tsbuildinfo +1 -1
  451. package/package.json +56 -28
  452. package/dist/src/app/assets/Report-CQYFezYu.js +0 -1
  453. package/dist/src/app/assets/index-BXGkeMwh.css +0 -1
  454. package/dist/src/app/assets/index-BzJt18Jz.js +0 -385
  455. package/dist/src/app/assets/sync-IjzpWrOE.js +0 -4
  456. package/dist/src/app/assets/vendor-charts-BNdH8TCw.js +0 -36
  457. package/dist/src/cache-Cr9oLMUa.js +0 -3
  458. package/dist/src/cache-DbLsVWB2.cjs +0 -3
  459. package/dist/src/cloud-Hphvo8kr.js +0 -3
  460. package/dist/src/codex-sdk-BAmYE7qy.js +0 -3
  461. package/dist/src/codex-sdk-CWEnH70W.cjs +0 -2
  462. package/dist/src/evalResult-D8MT9p0s.js +0 -3
  463. package/dist/src/evalResult-DElBuddX.js +0 -2
  464. package/dist/src/evalResult-Dvc-iucu.cjs +0 -2
  465. package/dist/src/evaluator-CVessDWe.js +0 -3
  466. package/dist/src/fetch-C7bGKDlQ.js +0 -3
  467. package/dist/src/graders-BOAzQEUe.cjs +0 -2
  468. package/dist/src/graders-D4BTsZdG2.js +0 -3
  469. package/dist/src/graders-DOJK1XpV.js +0 -2
  470. package/dist/src/graders-NAv9LcBn.js +0 -2
  471. package/dist/src/image-B5Mv-Z3h.js +0 -257
  472. package/dist/src/image-DVz2RiMF.js +0 -258
  473. package/dist/src/image-qUpPvmNZ.js +0 -257
  474. package/dist/src/image-x6KqLQl4.cjs +0 -280
  475. package/dist/src/providers-Bp4S-FvO.js +0 -2
  476. package/dist/src/providers-DV3ax9e_.cjs +0 -3
  477. package/dist/src/providers-u9Enmfok.js +0 -2
  478. package/dist/src/render-CH-62LbA.js +0 -135
  479. package/dist/src/render-CMEpfLaO.js +0 -136
  480. package/dist/src/render-DHIZ6_k8.js +0 -135
  481. package/dist/src/render-DfQSFxGE.cjs +0 -165
  482. package/dist/src/rubyUtils-D1L2d3jb.js +0 -3
  483. package/dist/src/rubyUtils-DUbq4tff.cjs +0 -2
  484. package/dist/src/server-BNYztJkh.js +0 -385
  485. package/dist/src/server-DCtHUqlp.js +0 -3
  486. package/dist/src/server-DaA2eR26.cjs +0 -2
  487. package/dist/src/store-CWOSz6D_.cjs +0 -2
  488. package/dist/src/store-DCDBhv7B.js +0 -3
  489. package/dist/src/store-Dn9HUkdW.js +0 -240
  490. package/dist/src/telemetry-C1IqxcdW.js +0 -3
  491. package/dist/src/telemetry-C4ZEa_es.cjs +0 -2
  492. package/dist/src/transform-Bbg6A8Jk.js +0 -216
  493. package/dist/src/transform-CUnzlsbn.cjs +0 -228
  494. package/dist/src/transform-DYX1_Xnh.js +0 -216
  495. package/dist/src/transform-DgKlRr73.cjs +0 -2
  496. package/dist/src/transform-M6ITAESf.js +0 -3
  497. package/dist/src/transform-UN5UGu8U.js +0 -213
@@ -1,689 +1,821 @@
1
- import { S as getEnvBool, a as logger, f as sanitizeObject, k as state } from "./logger-Ct2S6Yx-.js";
2
- import { t as invariant } from "./invariant-Ddh24eXh.js";
3
- import { o as safeResolve, r as importModule, t as getDirectory } from "./esm-C7PnfdF8.js";
4
- import { r as runPython } from "./pythonUtils-C2UQ30Rz.js";
5
- import { i as isJavascriptFile, t as JAVASCRIPT_EXTENSIONS } from "./fileExtensions-DnqA1y9x.js";
6
- import { A as isApiProvider, g as ResultFailureReason, h as OutputFileExtension, j as isProviderOptions } from "./types-BGQDAP8i.js";
7
- import { a as getNunjucksEngine, n as renderVarsInObject } from "./render-CH-62LbA.js";
8
- import { A as TERMINAL_MAX_WIDTH, F as VERSION, t as fetchWithProxy } from "./fetch-Di00EQrc.js";
1
+ import { S as getEnvBool, a as logger, f as sanitizeObject, k as state } from "./logger-KD8JjCRJ.js";
2
+ import { N as TERMINAL_MAX_WIDTH, t as fetchWithProxy } from "./fetch-BufrQtvR.js";
3
+ import { n as VERSION } from "./version-BK20a4sw.js";
4
+ import { t as invariant } from "./invariant-DIYf9sP1.js";
5
+ import { o as safeResolve, r as importModule, t as getDirectory } from "./esm-B6whoAcf.js";
6
+ import { n as renderVarsInObject, o as getNunjucksEngine } from "./render-_6ur1fhE.js";
7
+ import { m as isProviderOptions, o as OutputFileExtension, p as isApiProvider, s as ResultFailureReason } from "./types-BJQBBPTP.js";
8
+ import { o as isJavascriptFile, r as JAVASCRIPT_EXTENSIONS } from "./shared-BoG7qLMv.js";
9
+ import { r as runPython } from "./pythonUtils-CLCgQ9tt.js";
10
+ import dotenv from "dotenv";
9
11
  import * as fs$2 from "fs";
12
+ import fs from "fs";
10
13
  import * as path$1 from "path";
11
14
  import path from "path";
12
- import yaml from "js-yaml";
13
- import dotenv from "dotenv";
14
15
  import * as os$1 from "os";
15
- import dedent from "dedent";
16
+ import yaml from "js-yaml";
16
17
  import * as fsPromises from "fs/promises";
18
+ import dedent from "dedent";
19
+ import nunjucks from "nunjucks";
20
+ import { parse as parse$1 } from "csv-parse/sync";
17
21
  import { globSync, hasMagic } from "glob";
18
22
  import deepEqual from "fast-deep-equal";
19
- import { parse as parse$1 } from "csv-parse/sync";
20
- import nunjucks from "nunjucks";
21
23
  import { XMLBuilder } from "fast-xml-parser";
22
24
  import { stringify } from "csv-stringify/sync";
23
- //#region src/util/provider.ts
24
- function canonicalizeProviderId(id) {
25
- if (id.startsWith("file://")) {
26
- const filePath = id.slice(7);
27
- return path$1.isAbsolute(filePath) ? id : `file://${path$1.resolve(filePath)}`;
28
- }
29
- for (const prefix of [
30
- "exec:",
31
- "python:",
32
- "golang:"
33
- ]) if (id.startsWith(prefix)) {
34
- const filePath = id.slice(prefix.length);
35
- if (filePath.includes("/") || filePath.includes("\\")) return `${prefix}${path$1.resolve(filePath)}`;
36
- return id;
25
+ //#region src/util/functions/loadFunction.ts
26
+ const functionCache = {};
27
+ /**
28
+ * Loads a function from a JavaScript or Python file
29
+ * @param options Options for loading the function
30
+ * @returns The loaded function
31
+ */
32
+ async function loadFunction({ filePath, functionName, defaultFunctionName = "func", basePath = state.basePath, useCache = true }) {
33
+ const cacheKey = `${filePath}${functionName ? `:${functionName}` : ""}`;
34
+ if (useCache && functionCache[cacheKey]) return functionCache[cacheKey];
35
+ const resolvedPath = basePath ? path.resolve(basePath, filePath) : filePath;
36
+ if (!isJavascriptFile(resolvedPath) && !resolvedPath.endsWith(".py")) throw new Error(`File must be a JavaScript (${JAVASCRIPT_EXTENSIONS.join(", ")}) or Python (.py) file`);
37
+ try {
38
+ let func;
39
+ if (isJavascriptFile(resolvedPath)) {
40
+ const module = await importModule(resolvedPath, functionName);
41
+ let moduleFunc;
42
+ if (functionName) moduleFunc = module;
43
+ else moduleFunc = typeof module === "function" ? module : module?.default?.default || module?.default || module?.[defaultFunctionName] || module;
44
+ if (typeof moduleFunc !== "function") throw new Error(functionName ? `JavaScript file must export a "${functionName}" function` : `JavaScript file must export a function (as default export or named export "${defaultFunctionName}")`);
45
+ func = moduleFunc;
46
+ } else {
47
+ const result = (...args) => runPython(resolvedPath, functionName || defaultFunctionName, args);
48
+ func = result;
49
+ }
50
+ if (useCache) functionCache[cacheKey] = func;
51
+ return func;
52
+ } catch (err) {
53
+ logger.error(`Failed to load function: ${err.message}`);
54
+ throw err;
37
55
  }
38
- if ((id.endsWith(".js") || id.endsWith(".ts") || id.endsWith(".mjs")) && (id.includes("/") || id.includes("\\"))) return `file://${path$1.resolve(id)}`;
39
- return id;
40
- }
41
- function getProviderLabel(provider) {
42
- return provider?.label && typeof provider.label === "string" ? provider.label : void 0;
43
56
  }
44
- function providerToIdentifier(provider) {
45
- if (!provider) return;
46
- if (typeof provider === "string") return canonicalizeProviderId(provider);
47
- const label = getProviderLabel(provider);
48
- if (label) return label;
49
- if (isApiProvider(provider)) return canonicalizeProviderId(provider.id());
50
- if (isProviderOptions(provider)) {
51
- if (provider.id) return canonicalizeProviderId(provider.id);
52
- return;
53
- }
54
- if (typeof provider === "object" && "id" in provider && typeof provider.id === "string") return canonicalizeProviderId(provider.id);
57
+ /**
58
+ * Extracts the file path and function name from a file:// URL
59
+ * @param fileUrl The file:// URL (e.g., "file://path/to/file.js:functionName")
60
+ * @returns The file path and optional function name
61
+ */
62
+ function parseFileUrl(fileUrl) {
63
+ if (!fileUrl.startsWith("file://")) throw new Error("URL must start with file://");
64
+ const urlWithoutProtocol = fileUrl.slice(7);
65
+ const lastColonIndex = urlWithoutProtocol.lastIndexOf(":");
66
+ if (lastColonIndex > 1) return {
67
+ filePath: urlWithoutProtocol.slice(0, lastColonIndex),
68
+ functionName: urlWithoutProtocol.slice(lastColonIndex + 1)
69
+ };
70
+ return { filePath: urlWithoutProtocol };
55
71
  }
72
+ //#endregion
73
+ //#region src/util/file.ts
56
74
  /**
57
- * Gets a descriptive identifier string for a provider, showing both label and ID when both exist.
58
- * Useful for error messages to help users debug provider reference issues.
75
+ * Simple Nunjucks engine specifically for file paths
76
+ * This function is separate from the main getNunjucksEngine to avoid circular dependencies
59
77
  */
60
- function getProviderDescription(provider) {
61
- const label = provider.label;
62
- const id = provider.id();
63
- if (label && label !== id) return `${label} (${id})`;
64
- return id;
78
+ function getNunjucksEngineForFilePath() {
79
+ const env = nunjucks.configure({ autoescape: false });
80
+ env.addGlobal("env", {
81
+ ...process.env,
82
+ ...state.config?.env
83
+ });
84
+ return env;
65
85
  }
66
86
  /**
67
- * Checks if a provider reference matches a given provider.
68
- * Supports exact matching and wildcard patterns.
87
+ * Loads content from an external file if the input is a file path, otherwise
88
+ * returns the input as-is. Supports Nunjucks templating for file paths.
89
+ *
90
+ * @param filePath - The input to process. Can be a file path string starting with "file://",
91
+ * an array of file paths, or any other type of data.
92
+ * @param context - Optional context to control file loading behavior. 'assertion' context
93
+ * preserves Python/JS file references instead of loading their content.
94
+ * @returns The loaded content if the input was a file path, otherwise the original input.
95
+ * For JSON and YAML files, the content is parsed into an object.
96
+ * For other file types, the raw file content is returned as a string.
97
+ *
98
+ * @throws {Error} If the specified file does not exist.
69
99
  */
70
- function doesProviderRefMatch(ref, provider) {
71
- const label = provider.label;
72
- const id = provider.id();
73
- const canonicalRef = canonicalizeProviderId(ref);
74
- const canonicalId = canonicalizeProviderId(id);
75
- if (label && label === ref) return true;
76
- if (id === ref || canonicalId === canonicalRef) return true;
77
- if (ref.endsWith("*")) {
78
- const prefix = ref.slice(0, -1);
79
- if (label?.startsWith(prefix) || id.startsWith(prefix) || canonicalId.startsWith(prefix)) return true;
100
+ function maybeLoadFromExternalFile(filePath, context) {
101
+ if (Array.isArray(filePath)) return filePath.map((path) => {
102
+ return maybeLoadFromExternalFile(path, context);
103
+ });
104
+ if (typeof filePath !== "string") return filePath;
105
+ if (!filePath.startsWith("file://")) return filePath;
106
+ const renderedFilePath = getNunjucksEngineForFilePath().renderString(filePath, {});
107
+ const { filePath: cleanPath, functionName } = parseFileUrl(renderedFilePath);
108
+ if (context === "assertion" && (cleanPath.endsWith(".py") || isJavascriptFile(cleanPath))) {
109
+ logger.debug(`Preserving Python/JS file reference in assertion context: ${renderedFilePath}`);
110
+ return renderedFilePath;
80
111
  }
81
- if (label?.startsWith(`${ref}:`) || id.startsWith(`${ref}:`) || canonicalId.startsWith(`${ref}:`)) return true;
82
- return false;
112
+ if (context === "vars") {
113
+ logger.debug(`Preserving file reference in vars context: ${renderedFilePath}`);
114
+ return renderedFilePath;
115
+ }
116
+ if (functionName && (cleanPath.endsWith(".py") || isJavascriptFile(cleanPath))) return renderedFilePath;
117
+ const pathToUse = functionName && !(cleanPath.endsWith(".py") || isJavascriptFile(cleanPath)) ? renderedFilePath.slice(7) : cleanPath;
118
+ const resolvedPath = path$1.resolve(state.basePath || "", pathToUse);
119
+ if (hasMagic(pathToUse)) {
120
+ const matchedFiles = globSync(resolvedPath, { windowsPathsNoEscape: true });
121
+ if (matchedFiles.length === 0) throw new Error(`No files found matching pattern: ${resolvedPath}`);
122
+ const allContents = [];
123
+ for (const matchedFile of matchedFiles) {
124
+ let contents;
125
+ try {
126
+ contents = fs$2.readFileSync(matchedFile, "utf8");
127
+ } catch (error) {
128
+ if (error.code === "ENOENT") {
129
+ logger.debug(`File disappeared during glob expansion: ${matchedFile}`);
130
+ continue;
131
+ }
132
+ throw error;
133
+ }
134
+ if (matchedFile.endsWith(".json")) {
135
+ const parsed = JSON.parse(contents);
136
+ if (Array.isArray(parsed)) allContents.push(...parsed);
137
+ else allContents.push(parsed);
138
+ } else if (matchedFile.endsWith(".yaml") || matchedFile.endsWith(".yml")) {
139
+ const parsed = yaml.load(contents);
140
+ if (parsed === null || parsed === void 0) continue;
141
+ if (Array.isArray(parsed)) allContents.push(...parsed);
142
+ else allContents.push(parsed);
143
+ } else if (matchedFile.endsWith(".csv")) {
144
+ const records = parse$1(contents, { columns: true });
145
+ if (records.length > 0 && Object.keys(records[0]).length === 1) allContents.push(...records.map((record) => Object.values(record)[0]));
146
+ else allContents.push(...records);
147
+ } else allContents.push(contents);
148
+ }
149
+ return allContents;
150
+ }
151
+ const finalPath = resolvedPath;
152
+ let contents;
153
+ try {
154
+ contents = fs$2.readFileSync(finalPath, "utf8");
155
+ } catch (error) {
156
+ if (error.code === "ENOENT") throw new Error(`File does not exist: ${finalPath}`);
157
+ throw new Error(`Failed to read file ${finalPath}: ${error}`);
158
+ }
159
+ if (finalPath.endsWith(".json")) try {
160
+ return JSON.parse(contents);
161
+ } catch (error) {
162
+ throw new Error(`Failed to parse JSON file ${finalPath}: ${error}`);
163
+ }
164
+ if (finalPath.endsWith(".yaml") || finalPath.endsWith(".yml")) try {
165
+ return yaml.load(contents);
166
+ } catch (error) {
167
+ throw new Error(`Failed to parse YAML file ${finalPath}: ${error}`);
168
+ }
169
+ if (finalPath.endsWith(".csv")) {
170
+ const records = parse$1(contents, { columns: true });
171
+ if (records.length > 0 && Object.keys(records[0]).length === 1) return records.map((record) => Object.values(record)[0]);
172
+ return records;
173
+ }
174
+ return contents;
83
175
  }
84
176
  /**
85
- * Checks if a provider is allowed based on a list of allowed references.
177
+ * Resolves a relative file path with respect to a base path, handling cloud configuration appropriately.
178
+ * When using a cloud configuration, the current working directory is always used instead of the context's base path.
179
+ *
180
+ * @param filePath - The relative or absolute file path to resolve.
181
+ * @param isCloudConfig - Whether this is a cloud configuration.
182
+ * @returns The resolved absolute file path.
86
183
  */
87
- function isProviderAllowed(provider, allowedProviders) {
88
- if (!Array.isArray(allowedProviders)) return true;
89
- if (allowedProviders.length === 0) return false;
90
- return allowedProviders.some((ref) => doesProviderRefMatch(ref, provider));
184
+ function getResolvedRelativePath(filePath, isCloudConfig) {
185
+ if (path$1.isAbsolute(filePath) || !isCloudConfig) return filePath;
186
+ return path$1.join(process.cwd(), filePath);
91
187
  }
92
188
  /**
93
- * Detects if a provider uses OpenAI models.
94
- * This includes direct OpenAI providers and Azure OpenAI.
189
+ * Recursively loads external file references from a configuration object.
190
+ *
191
+ * @param config - The configuration object to process
192
+ * @param context - Optional context to control file loading behavior
193
+ * @returns The configuration with external file references resolved
95
194
  */
96
- function isOpenAiProvider(providerId) {
97
- const lowerProviderId = providerId.toLowerCase();
98
- if (lowerProviderId.startsWith("openai:")) return true;
99
- if (lowerProviderId.startsWith("azureopenai:")) return true;
100
- if (lowerProviderId.startsWith("azure:")) {
101
- if ([
102
- "gpt",
103
- "openai",
104
- "davinci",
105
- "curie",
106
- "babbage",
107
- "ada",
108
- "text-embedding",
109
- "whisper",
110
- "dall-e",
111
- "tts"
112
- ].some((indicator) => lowerProviderId.includes(indicator))) return true;
195
+ function maybeLoadConfigFromExternalFile(config, context) {
196
+ if (Array.isArray(config)) return config.map((item) => maybeLoadConfigFromExternalFile(item, context));
197
+ if (typeof config === "object" && config !== null) {
198
+ const result = {};
199
+ for (const key of Object.keys(config)) {
200
+ const childContext = key === "value" && "type" in config && typeof config.type === "string" && (config.type === "python" || config.type === "javascript") ? "assertion" : key === "vars" ? "vars" : context;
201
+ const value = maybeLoadConfigFromExternalFile(config[key], childContext);
202
+ if (key === "__proto__") Object.defineProperty(result, key, {
203
+ value,
204
+ enumerable: true,
205
+ configurable: true,
206
+ writable: true
207
+ });
208
+ else result[key] = value;
209
+ }
210
+ return result;
113
211
  }
114
- return false;
212
+ return maybeLoadFromExternalFile(config, context);
115
213
  }
116
214
  /**
117
- * Detects if a provider uses Anthropic/Claude models.
118
- * This includes direct Anthropic providers, Bedrock with Claude, and Vertex with Claude.
215
+ * Parses a file path or glob pattern to extract function names and file extensions.
216
+ * Function names can be specified in the filename like this:
217
+ * prompt.py:myFunction or prompts.js:myFunction.
218
+ * @param basePath - The base path for file resolution.
219
+ * @param promptPath - The path or glob pattern.
220
+ * @returns Parsed details including function name, file extension, and directory status.
119
221
  */
120
- function isAnthropicProvider(providerId) {
121
- const lowerProviderId = providerId.toLowerCase();
122
- if (lowerProviderId.startsWith("anthropic:")) return true;
123
- if (lowerProviderId.startsWith("bedrock:")) {
124
- if (lowerProviderId.includes("claude") || lowerProviderId.includes("anthropic")) return true;
222
+ function parsePathOrGlob(basePath, promptPath) {
223
+ if (promptPath.startsWith("file://")) promptPath = promptPath.slice(7);
224
+ const filePath = path$1.resolve(basePath, promptPath);
225
+ let filename = path$1.relative(basePath, filePath);
226
+ let functionName;
227
+ if (filename.includes(":")) {
228
+ const lastColonIndex = filename.lastIndexOf(":");
229
+ if (lastColonIndex > 1) {
230
+ const pathWithoutFunction = filename.slice(0, lastColonIndex);
231
+ if (isJavascriptFile(pathWithoutFunction) || pathWithoutFunction.endsWith(".py") || pathWithoutFunction.endsWith(".go") || pathWithoutFunction.endsWith(".rb")) {
232
+ functionName = filename.slice(lastColonIndex + 1);
233
+ filename = pathWithoutFunction;
234
+ }
235
+ }
125
236
  }
126
- if (lowerProviderId.startsWith("vertex:")) {
127
- if (lowerProviderId.includes("claude")) return true;
237
+ let stats;
238
+ try {
239
+ stats = fs$2.statSync(path$1.join(basePath, filename));
240
+ } catch (err) {
241
+ if (getEnvBool("PROMPTFOO_STRICT_FILES")) throw err;
128
242
  }
129
- return false;
130
- }
131
- const KNOWN_ENV_VARS = {
132
- openai: "OPENAI_API_KEY",
133
- anthropic: "ANTHROPIC_API_KEY",
134
- google: "GOOGLE_API_KEY",
135
- mistral: "MISTRAL_API_KEY",
136
- cohere: "COHERE_API_KEY",
137
- replicate: "REPLICATE_API_TOKEN",
138
- voyage: "VOYAGE_API_KEY",
139
- ai21: "AI21_API_KEY",
140
- xai: "XAI_API_KEY",
141
- groq: "GROQ_API_KEY",
142
- deepseek: "DEEPSEEK_API_KEY",
143
- perplexity: "PERPLEXITY_API_KEY",
144
- hyperbolic: "HYPERBOLIC_API_KEY",
145
- cerebras: "CEREBRAS_API_KEY",
146
- togetherai: "TOGETHER_API_KEY",
147
- fal: "FAL_KEY",
148
- huggingface: "HF_TOKEN",
149
- "cloudflare-ai": "CLOUDFLARE_API_KEY"
150
- };
151
- function getDefaultEnvVar(providerId) {
152
- const prefix = providerId.split(":")[0];
153
- return KNOWN_ENV_VARS[prefix] || `${prefix.toUpperCase()}_API_KEY`;
243
+ const normalizedFilePath = filePath.replace(/\\/g, "/");
244
+ const isPathPattern = stats?.isDirectory() || hasMagic(promptPath) || hasMagic(normalizedFilePath);
245
+ const safeFilename = path$1.relative(basePath, safeResolve(basePath, filename));
246
+ return {
247
+ extension: isPathPattern ? void 0 : path$1.parse(safeFilename).ext,
248
+ filePath: path$1.join(basePath, safeFilename),
249
+ functionName,
250
+ isPathPattern
251
+ };
154
252
  }
155
- /**
156
- * Pre-checks providers for missing API keys before evaluation starts.
157
- * Assumes getApiKey() is side-effect free (no network calls or token refresh).
158
- */
159
- function checkProviderApiKeys(providers) {
160
- const missingApiKeys = /* @__PURE__ */ new Map();
161
- for (const provider of providers) {
162
- const p = provider;
163
- if (typeof p.getApiKey !== "function") continue;
164
- if (provider.id().startsWith("azure:")) continue;
165
- const requiresKey = typeof p.requiresApiKey === "function" ? p.requiresApiKey() : p.config?.apiKeyRequired !== false;
166
- let apiKey;
167
- try {
168
- apiKey = p.getApiKey();
169
- } catch {
170
- apiKey = void 0;
171
- }
172
- if (requiresKey && !apiKey) {
173
- const envVar = p.config?.apiKeyEnvar || getDefaultEnvVar(provider.id());
174
- if (!missingApiKeys.has(envVar)) missingApiKeys.set(envVar, []);
175
- missingApiKeys.get(envVar).push(provider.id());
176
- }
253
+ function readOutput(outputPath) {
254
+ const ext = path$1.parse(outputPath).ext.slice(1);
255
+ switch (ext) {
256
+ case "json": return JSON.parse(fs$2.readFileSync(outputPath, "utf-8"));
257
+ default: throw new Error(`Unsupported output file format: ${ext} currently only supports json`);
177
258
  }
178
- return missingApiKeys;
179
259
  }
180
260
  /**
181
- * Detects if a provider uses Google models.
182
- * This includes direct Google/Vertex providers with Gemini and other Google models.
183
- * Note: Vertex with Claude models is NOT counted as Google (it's Anthropic).
261
+ * Load custom Nunjucks filters from external files.
262
+ * Note: If a glob pattern matches multiple files, only the last file's export is used.
263
+ * Each filter name should typically resolve to a single file.
184
264
  */
185
- function isGoogleProvider(providerId) {
186
- const lowerProviderId = providerId.toLowerCase();
187
- if (lowerProviderId.startsWith("google:")) return true;
188
- if (lowerProviderId.startsWith("vertex:")) {
189
- if (!lowerProviderId.includes("claude")) return true;
265
+ async function readFilters(filters, basePath = "") {
266
+ const ret = {};
267
+ for (const [name, filterPath] of Object.entries(filters)) {
268
+ const filePaths = globSync(path$1.join(basePath, filterPath), { windowsPathsNoEscape: true });
269
+ for (const filePath of filePaths) ret[name] = await importModule(path$1.resolve(filePath));
190
270
  }
191
- return false;
271
+ return ret;
192
272
  }
193
- //#endregion
194
- //#region src/util/comparison.ts
195
273
  /**
196
- * Explicit runtime variable names that don't follow the underscore convention.
197
- * These are added during evaluation but aren't part of the original test definition.
274
+ * Loads configuration from an external file with variable rendering.
275
+ * This is a convenience wrapper that combines renderVarsInObject and maybeLoadFromExternalFile.
198
276
  *
199
- * - sessionId: Added by multi-turn strategy providers (GOAT, Crescendo)
277
+ * Use this for simple config fields that:
278
+ * - Need variable rendering ({{ vars.x }}, {{ env.X }})
279
+ * - May reference external files (file://path.json)
280
+ * - Don't have nested file references that need loading
200
281
  *
201
- * Note: Variables starting with underscore (e.g., _conversation) are automatically
202
- * treated as runtime variables and filtered out.
203
- */
204
- const EXPLICIT_RUNTIME_VAR_KEYS = ["sessionId"];
205
- /**
206
- * Checks if a variable key is a runtime-only variable that should be filtered
207
- * when comparing test cases.
282
+ * For fields with nested file references (like response_format.schema),
283
+ * use maybeLoadResponseFormatFromExternalFile instead.
208
284
  *
209
- * Runtime variables are identified by:
210
- * 1. Starting with underscore (_) - convention for internal/runtime vars
211
- * 2. Being in the explicit runtime var list (for legacy vars like sessionId)
285
+ * @param config - The configuration to process
286
+ * @param vars - Variables for template rendering
287
+ * @returns The processed configuration with variables rendered and files loaded
212
288
  */
213
- function isRuntimeVar(key) {
214
- return key.startsWith("_") || EXPLICIT_RUNTIME_VAR_KEYS.includes(key);
289
+ function maybeLoadFromExternalFileWithVars(config, vars) {
290
+ return maybeLoadFromExternalFile(renderVarsInObject(config, vars));
215
291
  }
216
292
  /**
217
- * Filters out runtime-only variables that are added during evaluation
218
- * but aren't part of the original test definition.
293
+ * Loads response_format configuration from an external file with variable rendering.
219
294
  *
220
- * This is used when comparing test cases to determine if a result
221
- * corresponds to a particular test, regardless of runtime state.
295
+ * This function handles the special case where response_format may contain:
296
+ * 1. A top-level file reference (file://format.json)
297
+ * 2. A nested schema reference for json_schema type (schema: file://schema.json)
222
298
  *
223
- * Runtime variables are identified by:
224
- * - Starting with underscore (e.g., _conversation, _metadata)
225
- * - Being in the explicit list (e.g., sessionId for backward compatibility)
299
+ * Both levels need variable rendering and file loading.
300
+ *
301
+ * @param responseFormat - The response_format configuration
302
+ * @param vars - Variables for template rendering
303
+ * @returns The processed response_format with all files loaded
226
304
  */
227
- function filterRuntimeVars(vars) {
228
- if (!vars || typeof vars !== "object" || Array.isArray(vars)) return vars;
229
- const filtered = {};
230
- for (const [key, value] of Object.entries(vars)) if (!isRuntimeVar(key)) filtered[key] = value;
231
- return filtered;
305
+ function maybeLoadResponseFormatFromExternalFile(responseFormat, vars) {
306
+ if (responseFormat === void 0 || responseFormat === null) return responseFormat;
307
+ const loaded = maybeLoadFromExternalFile(renderVarsInObject(responseFormat, vars));
308
+ if (!loaded || typeof loaded !== "object") return loaded;
309
+ if (loaded.type === "json_schema") {
310
+ const nestedSchema = loaded.schema || loaded.json_schema?.schema;
311
+ if (nestedSchema) {
312
+ const loadedSchema = maybeLoadFromExternalFile(renderVarsInObject(nestedSchema, vars));
313
+ if (loaded.schema !== void 0) return {
314
+ ...loaded,
315
+ schema: loadedSchema
316
+ };
317
+ else if (loaded.json_schema?.schema !== void 0) return {
318
+ ...loaded,
319
+ json_schema: {
320
+ ...loaded.json_schema,
321
+ schema: loadedSchema
322
+ }
323
+ };
324
+ }
325
+ }
326
+ return loaded;
232
327
  }
233
328
  /**
234
- * Extracts only runtime variables from a vars object.
235
- * This is the inverse of filterRuntimeVars.
329
+ * Renders variables in a tools object and loads from external file if applicable.
330
+ * This function combines renderVarsInObject and maybeLoadFromExternalFile into a single step
331
+ * specifically for handling tools configurations.
236
332
  *
237
- * Used to restore runtime state when re-running filtered tests.
333
+ * Supports loading from JSON, YAML, Python, and JavaScript files.
334
+ *
335
+ * @param tools - The tools configuration object or array to process.
336
+ * @param vars - Variables to use for rendering.
337
+ * @returns The processed tools configuration with variables rendered and content loaded from files if needed.
338
+ * @throws {Error} If the loaded tools are in an invalid format
238
339
  */
239
- function extractRuntimeVars(vars) {
240
- if (!vars || typeof vars !== "object" || Array.isArray(vars)) return;
241
- const extracted = {};
242
- for (const [key, value] of Object.entries(vars)) if (isRuntimeVar(key)) extracted[key] = value;
243
- return Object.keys(extracted).length > 0 ? extracted : void 0;
340
+ async function maybeLoadToolsFromExternalFile(tools, vars) {
341
+ const rendered = renderVarsInObject(tools, vars);
342
+ if (typeof rendered === "string" && rendered.startsWith("file://")) {
343
+ const { filePath, functionName } = parseFileUrl(rendered);
344
+ if (functionName && (filePath.endsWith(".py") || isJavascriptFile(filePath))) {
345
+ const fileType = filePath.endsWith(".py") ? "Python" : "JavaScript";
346
+ logger.debug(`[maybeLoadToolsFromExternalFile] Loading tools from ${fileType} file: ${filePath}:${functionName}`);
347
+ try {
348
+ let toolDefinitions;
349
+ if (filePath.endsWith(".py")) {
350
+ const absPath = safeResolve(state.basePath || process.cwd(), filePath);
351
+ logger.debug(`[maybeLoadToolsFromExternalFile] Resolved Python path: ${absPath}`);
352
+ toolDefinitions = await runPython(absPath, functionName, []);
353
+ } else {
354
+ const absPath = safeResolve(state.basePath || process.cwd(), filePath);
355
+ logger.debug(`[maybeLoadToolsFromExternalFile] Resolved JavaScript path: ${absPath}`);
356
+ const module = await importModule(absPath);
357
+ const fn = module[functionName] || module.default?.[functionName];
358
+ if (typeof fn !== "function") {
359
+ const availableExports = Object.keys(module).filter((k) => k !== "default");
360
+ const basePath = state.basePath || process.cwd();
361
+ throw new Error(`Function "${functionName}" not found in ${filePath}. Available exports: ${availableExports.length > 0 ? availableExports.join(", ") : "(none)"}\nResolved from: ${basePath}`);
362
+ }
363
+ toolDefinitions = await Promise.resolve(fn());
364
+ }
365
+ if (!toolDefinitions || typeof toolDefinitions === "string" || typeof toolDefinitions === "number" || typeof toolDefinitions === "boolean") throw new Error(`Function "${functionName}" must return an array or object of tool definitions, but returned: ${toolDefinitions === null ? "null" : typeof toolDefinitions}`);
366
+ logger.debug(`[maybeLoadToolsFromExternalFile] Successfully loaded ${Array.isArray(toolDefinitions) ? toolDefinitions.length : "object"} tools`);
367
+ return toolDefinitions;
368
+ } catch (err) {
369
+ const errorMessage = err instanceof Error ? err.message : String(err);
370
+ const basePath = state.basePath || process.cwd();
371
+ throw new Error(`Failed to load tools from ${rendered}:\n${errorMessage}\n\nMake sure the function "${functionName}" exists and returns a valid tool definition array.\nResolved from: ${basePath}`);
372
+ }
373
+ }
374
+ if (filePath.endsWith(".py") || isJavascriptFile(filePath)) {
375
+ const ext = filePath.endsWith(".py") ? "Python" : "JavaScript";
376
+ const basePath = state.basePath || process.cwd();
377
+ throw new Error(`Cannot load tools from ${rendered}\n${ext} files require a function name. Use this format:\n tools: file://${filePath}:get_tools\n\nYour ${ext} file should export a function that returns tool definitions:\n` + (filePath.endsWith(".py") ? ` def get_tools():\n return [{"type": "function", "function": {...}}]` : ` module.exports.get_tools = () => [{ type: "function", function: {...} }];`) + `\n\nResolved from: ${basePath}`);
378
+ }
379
+ }
380
+ if (Array.isArray(rendered)) {
381
+ const results = await Promise.all(rendered.map((item) => maybeLoadToolsFromExternalFile(item, vars)));
382
+ if (results.every((r) => Array.isArray(r))) return results.flat();
383
+ return results;
384
+ }
385
+ if (typeof rendered !== "string") return rendered;
386
+ const loaded = maybeLoadFromExternalFile(rendered);
387
+ if (loaded !== void 0 && loaded !== null && typeof loaded === "string") {
388
+ if (loaded.startsWith("file://")) throw new Error(`Failed to load tools from ${loaded}\nEnsure the file exists and contains valid JSON or YAML tool definitions.`);
389
+ if (loaded.includes("def ") || loaded.includes("import ")) throw new Error("Invalid tools configuration: file appears to contain Python code.\nPython files require a function name. Use this format:\n tools: file://tools.py:get_tools");
390
+ throw new Error("Invalid tools configuration: expected an array or object, but got a string.\nIf using file://, ensure the file contains valid JSON or YAML tool definitions.");
391
+ }
392
+ return loaded;
244
393
  }
245
- function varsMatch(vars1, vars2) {
246
- return deepEqual(vars1, vars2);
394
+ //#endregion
395
+ //#region src/util/providerRef.ts
396
+ const PROVIDER_OPTION_KEYS = new Set([
397
+ "id",
398
+ "label",
399
+ "config",
400
+ "prompts",
401
+ "transform",
402
+ "delay",
403
+ "env",
404
+ "inputs"
405
+ ]);
406
+ /** Returns true if the value is a non-empty string suitable as a provider identifier. */
407
+ function isValidProviderId(id) {
408
+ return typeof id === "string" && id !== "";
409
+ }
410
+ function getProviderLabel(provider) {
411
+ if ((typeof provider === "object" || typeof provider === "function") && provider !== null && "label" in provider && typeof provider.label === "string") return provider.label;
247
412
  }
248
413
  /**
249
- * Generate a unique key for a test case for deduplication purposes.
250
- * Excludes runtime variables and includes strategyId to distinguish tests
251
- * with the same prompt but different strategies.
252
- *
253
- * @param testCase - The test case to generate a key for
254
- * @returns A JSON string that uniquely identifies the test case
414
+ * Resolves relative file paths in provider IDs to absolute paths for consistent matching.
415
+ * Handles file://, exec:, python:, golang: prefixes and bare .js/.ts/.mjs paths.
255
416
  */
256
- function getTestCaseDeduplicationKey(testCase) {
257
- const filteredVars = filterRuntimeVars(testCase.vars);
258
- const strategyId = testCase.metadata?.strategyId || "none";
259
- return JSON.stringify({
260
- vars: filteredVars,
261
- strategyId
262
- });
263
- }
264
- /**
265
- * Deduplicates an array of test cases based on their vars and strategyId.
266
- * Tests with the same vars but different strategies are considered different.
267
- * Runtime variables (like _conversation, sessionId) are filtered out before comparison.
268
- *
269
- * @param tests - Array of test cases to deduplicate
270
- * @returns Deduplicated array of test cases
271
- */
272
- function deduplicateTestCases(tests) {
273
- const seen = /* @__PURE__ */ new Set();
274
- return tests.filter((test) => {
275
- const key = getTestCaseDeduplicationKey(test);
276
- if (seen.has(key)) return false;
277
- seen.add(key);
278
- return true;
279
- });
280
- }
281
- function resultIsForTestCase(result, testCase) {
282
- const testProviderId = testCase.provider ? providerToIdentifier(testCase.provider) : void 0;
283
- const resultProviderId = providerToIdentifier(result.provider);
284
- const providersMatch = !testProviderId || !resultProviderId || testProviderId === resultProviderId;
285
- const resultVars = filterRuntimeVars(result.vars);
286
- const testVars = filterRuntimeVars(testCase.vars);
287
- const doVarsMatch = varsMatch(testVars, resultVars);
288
- const isMatch = doVarsMatch && providersMatch;
289
- if (!isMatch) {
290
- const varKeys = testVars ? Object.keys(testVars).join(", ") : "none";
291
- logger.debug(`[resultIsForTestCase] No match: vars=${doVarsMatch}, providers=${providersMatch}`, {
292
- testProvider: testProviderId || "none",
293
- resultProvider: resultProviderId || "none",
294
- testVarKeys: varKeys
295
- });
417
+ function canonicalizeProviderId(id) {
418
+ if (id.startsWith("file://")) {
419
+ const filePath = id.slice(7);
420
+ return path.isAbsolute(filePath) ? id : `file://${path.resolve(filePath)}`;
296
421
  }
297
- return isMatch;
422
+ for (const prefix of [
423
+ "exec:",
424
+ "python:",
425
+ "golang:"
426
+ ]) if (id.startsWith(prefix)) {
427
+ const filePath = id.slice(prefix.length);
428
+ if (filePath.includes("/") || filePath.includes("\\")) return `${prefix}${path.resolve(filePath)}`;
429
+ return id;
430
+ }
431
+ if ((id.endsWith(".js") || id.endsWith(".ts") || id.endsWith(".mjs")) && (id.includes("/") || id.includes("\\"))) return `file://${path.resolve(id)}`;
432
+ return id;
298
433
  }
299
- //#endregion
300
- //#region src/util/env.ts
301
434
  /**
302
- * Load environment variables from .env file(s).
303
- * @param envPath - Single path, array of paths, or undefined for default .env loading.
304
- * When paths are explicitly specified, all files must exist or an error is thrown.
305
- * When multiple files are provided, later files override values from earlier files.
435
+ * Returns true for provider refs that should be expanded from YAML/JSON config files.
306
436
  */
307
- function setupEnv(envPath) {
308
- if (envPath) {
309
- const paths = (Array.isArray(envPath) ? envPath : [envPath]).flatMap((p) => p.includes(",") ? p.split(",").map((s) => s.trim()) : p.trim()).filter((p) => p.length > 0);
310
- if (paths.length === 0) {
311
- dotenv.config({ quiet: true });
312
- return;
313
- }
314
- for (const p of paths) if (!fs$2.existsSync(p)) throw new Error(`Environment file not found: ${p}`);
315
- if (paths.length === 1) logger.info(`Loading environment variables from ${paths[0]}`);
316
- else logger.info(`Loading environment variables from: ${paths.join(", ")}`);
317
- const pathArg = paths.length === 1 ? paths[0] : paths;
318
- dotenv.config({
319
- path: pathArg,
320
- override: true,
321
- quiet: true
322
- });
323
- } else dotenv.config({ quiet: true });
437
+ function isProviderConfigFileReference(providerPath) {
438
+ return providerPath.startsWith("file://") && (providerPath.endsWith(".yaml") || providerPath.endsWith(".yml") || providerPath.endsWith(".json"));
324
439
  }
325
- //#endregion
326
- //#region src/util/functions/loadFunction.ts
327
- const functionCache = {};
328
440
  /**
329
- * Loads a function from a JavaScript or Python file
330
- * @param options Options for loading the function
331
- * @returns The loaded function
441
+ * Reads a provider config file and normalizes single-provider and multi-provider files.
442
+ * Returns a `wasArray` flag so callers can detect multi-provider files that require
443
+ * `loadApiProviders` instead of `loadApiProvider`.
332
444
  */
333
- async function loadFunction({ filePath, functionName, defaultFunctionName = "func", basePath = state.basePath, useCache = true }) {
334
- const cacheKey = `${filePath}${functionName ? `:${functionName}` : ""}`;
335
- if (useCache && functionCache[cacheKey]) return functionCache[cacheKey];
336
- const resolvedPath = basePath ? path.resolve(basePath, filePath) : filePath;
337
- if (!isJavascriptFile(resolvedPath) && !resolvedPath.endsWith(".py")) throw new Error(`File must be a JavaScript (${JAVASCRIPT_EXTENSIONS.join(", ")}) or Python (.py) file`);
445
+ function readProviderConfigFile(providerPath, basePath) {
446
+ const relativePath = providerPath.slice(7);
447
+ const resolvedPath = path.isAbsolute(relativePath) ? relativePath : path.join(basePath || process.cwd(), relativePath);
448
+ let rawContent;
338
449
  try {
339
- let func;
340
- if (isJavascriptFile(resolvedPath)) {
341
- const module = await importModule(resolvedPath, functionName);
342
- let moduleFunc;
343
- if (functionName) moduleFunc = module;
344
- else moduleFunc = typeof module === "function" ? module : module?.default?.default || module?.default || module?.[defaultFunctionName] || module;
345
- if (typeof moduleFunc !== "function") throw new Error(functionName ? `JavaScript file must export a "${functionName}" function` : `JavaScript file must export a function (as default export or named export "${defaultFunctionName}")`);
346
- func = moduleFunc;
347
- } else {
348
- const result = (...args) => runPython(resolvedPath, functionName || defaultFunctionName, args);
349
- func = result;
350
- }
351
- if (useCache) functionCache[cacheKey] = func;
352
- return func;
450
+ rawContent = yaml.load(fs.readFileSync(resolvedPath, "utf8"));
353
451
  } catch (err) {
354
- logger.error(`Failed to load function: ${err.message}`);
355
- throw err;
452
+ throw new Error(`Failed to load provider config ${relativePath}: ${err instanceof Error ? err.message : err}`);
356
453
  }
357
- }
358
- /**
359
- * Extracts the file path and function name from a file:// URL
360
- * @param fileUrl The file:// URL (e.g., "file://path/to/file.js:functionName")
361
- * @returns The file path and optional function name
362
- */
363
- function parseFileUrl(fileUrl) {
364
- if (!fileUrl.startsWith("file://")) throw new Error("URL must start with file://");
365
- const urlWithoutProtocol = fileUrl.slice(7);
366
- const lastColonIndex = urlWithoutProtocol.lastIndexOf(":");
367
- if (lastColonIndex > 1) return {
368
- filePath: urlWithoutProtocol.slice(0, lastColonIndex),
369
- functionName: urlWithoutProtocol.slice(lastColonIndex + 1)
454
+ const fileContent = maybeLoadConfigFromExternalFile(rawContent);
455
+ invariant(fileContent, `Provider config ${relativePath} is undefined`);
456
+ return {
457
+ configs: [fileContent].flat(),
458
+ relativePath,
459
+ wasArray: Array.isArray(fileContent)
370
460
  };
371
- return { filePath: urlWithoutProtocol };
372
461
  }
373
- //#endregion
374
- //#region src/util/file.ts
375
462
  /**
376
- * Simple Nunjucks engine specifically for file paths
377
- * This function is separate from the main getNunjucksEngine to avoid circular dependencies
463
+ * Loads provider config objects from a file-backed provider reference.
378
464
  */
379
- function getNunjucksEngineForFilePath() {
380
- const env = nunjucks.configure({ autoescape: false });
381
- env.addGlobal("env", {
382
- ...process.env,
383
- ...state.config?.env
384
- });
385
- return env;
465
+ function loadProviderConfigsFromFile(providerPath, basePath) {
466
+ return readProviderConfigFile(providerPath, basePath).configs;
386
467
  }
387
468
  /**
388
- * Loads content from an external file if the input is a file path, otherwise
389
- * returns the input as-is. Supports Nunjucks templating for file paths.
390
- *
391
- * @param filePath - The input to process. Can be a file path string starting with "file://",
392
- * an array of file paths, or any other type of data.
393
- * @param context - Optional context to control file loading behavior. 'assertion' context
394
- * preserves Python/JS file references instead of loading their content.
395
- * @returns The loaded content if the input was a file path, otherwise the original input.
396
- * For JSON and YAML files, the content is parsed into an object.
397
- * For other file types, the raw file content is returned as a string.
398
- *
399
- * @throws {Error} If the specified file does not exist.
469
+ * Pure, synchronous classifier that converts every supported provider reference shape
470
+ * into a discriminated descriptor. Does not read files or instantiate providers.
400
471
  */
401
- function maybeLoadFromExternalFile(filePath, context) {
402
- if (Array.isArray(filePath)) return filePath.map((path) => {
403
- return maybeLoadFromExternalFile(path, context);
404
- });
405
- if (typeof filePath !== "string") return filePath;
406
- if (!filePath.startsWith("file://")) return filePath;
407
- const renderedFilePath = getNunjucksEngineForFilePath().renderString(filePath, {});
408
- const { filePath: cleanPath, functionName } = parseFileUrl(renderedFilePath);
409
- if (context === "assertion" && (cleanPath.endsWith(".py") || isJavascriptFile(cleanPath))) {
410
- logger.debug(`Preserving Python/JS file reference in assertion context: ${renderedFilePath}`);
411
- return renderedFilePath;
472
+ function normalizeProviderRef(provider, options = {}) {
473
+ const { index } = options;
474
+ if (typeof provider === "string") {
475
+ if (!isValidProviderId(provider)) return {
476
+ kind: "unknown",
477
+ id: index === void 0 ? "unknown" : `unknown-${index}`
478
+ };
479
+ if (isProviderConfigFileReference(provider)) return {
480
+ kind: "file",
481
+ id: provider,
482
+ loadProviderPath: provider
483
+ };
484
+ return {
485
+ kind: "named",
486
+ id: provider,
487
+ loadProviderPath: provider
488
+ };
412
489
  }
413
- if (context === "vars") {
414
- logger.debug(`Preserving file reference in vars context: ${renderedFilePath}`);
415
- return renderedFilePath;
490
+ if (typeof provider === "function") {
491
+ const label = getProviderLabel(provider);
492
+ return {
493
+ kind: "function",
494
+ id: label ?? (index === void 0 ? "custom-function" : `custom-function-${index}`),
495
+ label
496
+ };
416
497
  }
417
- if (functionName && (cleanPath.endsWith(".py") || isJavascriptFile(cleanPath))) return renderedFilePath;
418
- const pathToUse = functionName && !(cleanPath.endsWith(".py") || isJavascriptFile(cleanPath)) ? renderedFilePath.slice(7) : cleanPath;
419
- const resolvedPath = path$1.resolve(state.basePath || "", pathToUse);
420
- if (hasMagic(pathToUse)) {
421
- const matchedFiles = globSync(resolvedPath, { windowsPathsNoEscape: true });
422
- if (matchedFiles.length === 0) throw new Error(`No files found matching pattern: ${resolvedPath}`);
423
- const allContents = [];
424
- for (const matchedFile of matchedFiles) {
425
- let contents;
426
- try {
427
- contents = fs$2.readFileSync(matchedFile, "utf8");
428
- } catch (error) {
429
- if (error.code === "ENOENT") {
430
- logger.debug(`File disappeared during glob expansion: ${matchedFile}`);
431
- continue;
432
- }
433
- throw error;
498
+ if (typeof provider === "object" && provider !== null && !Array.isArray(provider)) {
499
+ const providerId = provider.id;
500
+ const label = getProviderLabel(provider);
501
+ if (isValidProviderId(providerId)) return {
502
+ kind: "options",
503
+ id: providerId,
504
+ label,
505
+ loadOptions: provider,
506
+ loadProviderPath: providerId
507
+ };
508
+ const keys = Object.keys(provider);
509
+ if (keys.length === 1 && !PROVIDER_OPTION_KEYS.has(keys[0])) {
510
+ const originalId = keys[0];
511
+ const providerObject = provider[originalId];
512
+ if (typeof providerObject === "object" && providerObject !== null && !Array.isArray(providerObject) && isValidProviderId(originalId)) {
513
+ const id = isValidProviderId(providerObject.id) ? providerObject.id : originalId;
514
+ return {
515
+ kind: "map",
516
+ id,
517
+ label: getProviderLabel(providerObject),
518
+ loadOptions: {
519
+ ...providerObject,
520
+ id
521
+ },
522
+ loadProviderPath: originalId
523
+ };
434
524
  }
435
- if (matchedFile.endsWith(".json")) {
436
- const parsed = JSON.parse(contents);
437
- if (Array.isArray(parsed)) allContents.push(...parsed);
438
- else allContents.push(parsed);
439
- } else if (matchedFile.endsWith(".yaml") || matchedFile.endsWith(".yml")) {
440
- const parsed = yaml.load(contents);
441
- if (parsed === null || parsed === void 0) continue;
442
- if (Array.isArray(parsed)) allContents.push(...parsed);
443
- else allContents.push(parsed);
444
- } else if (matchedFile.endsWith(".csv")) {
445
- const records = parse$1(contents, { columns: true });
446
- if (records.length > 0 && Object.keys(records[0]).length === 1) allContents.push(...records.map((record) => Object.values(record)[0]));
447
- else allContents.push(...records);
448
- } else allContents.push(contents);
449
525
  }
450
- return allContents;
451
- }
452
- const finalPath = resolvedPath;
453
- let contents;
454
- try {
455
- contents = fs$2.readFileSync(finalPath, "utf8");
456
- } catch (error) {
457
- if (error.code === "ENOENT") throw new Error(`File does not exist: ${finalPath}`);
458
- throw new Error(`Failed to read file ${finalPath}: ${error}`);
459
- }
460
- if (finalPath.endsWith(".json")) try {
461
- return JSON.parse(contents);
462
- } catch (error) {
463
- throw new Error(`Failed to parse JSON file ${finalPath}: ${error}`);
526
+ if (isValidProviderId(label)) return {
527
+ kind: "unknown",
528
+ id: label,
529
+ label
530
+ };
464
531
  }
465
- if (finalPath.endsWith(".yaml") || finalPath.endsWith(".yml")) try {
466
- return yaml.load(contents);
467
- } catch (error) {
468
- throw new Error(`Failed to parse YAML file ${finalPath}: ${error}`);
532
+ return {
533
+ kind: "unknown",
534
+ id: index === void 0 ? "unknown" : `unknown-${index}`
535
+ };
536
+ }
537
+ //#endregion
538
+ //#region src/util/provider.ts
539
+ function providerToIdentifier(provider) {
540
+ if (!provider) return;
541
+ if (typeof provider === "string") return canonicalizeProviderId(provider);
542
+ const { label } = normalizeProviderRef(provider);
543
+ if (label) return label;
544
+ if (isApiProvider(provider)) return canonicalizeProviderId(provider.id());
545
+ if (isProviderOptions(provider)) {
546
+ if (provider.id) return canonicalizeProviderId(provider.id);
547
+ return;
469
548
  }
470
- if (finalPath.endsWith(".csv")) {
471
- const records = parse$1(contents, { columns: true });
472
- if (records.length > 0 && Object.keys(records[0]).length === 1) return records.map((record) => Object.values(record)[0]);
473
- return records;
549
+ if (typeof provider === "object" && "id" in provider && typeof provider.id === "string") return canonicalizeProviderId(provider.id);
550
+ }
551
+ /**
552
+ * Gets a descriptive identifier string for a provider, showing both label and ID when both exist.
553
+ * Useful for error messages to help users debug provider reference issues.
554
+ */
555
+ function getProviderDescription(provider) {
556
+ const label = provider.label;
557
+ const id = provider.id();
558
+ if (label && label !== id) return `${label} (${id})`;
559
+ return id;
560
+ }
561
+ /**
562
+ * Checks if a provider reference matches a given provider.
563
+ * Supports exact matching and wildcard patterns.
564
+ */
565
+ function doesProviderRefMatch(ref, provider) {
566
+ const label = provider.label;
567
+ const id = provider.id();
568
+ const canonicalRef = canonicalizeProviderId(ref);
569
+ const canonicalId = canonicalizeProviderId(id);
570
+ if (label && label === ref) return true;
571
+ if (id === ref || canonicalId === canonicalRef) return true;
572
+ if (ref.endsWith("*")) {
573
+ const prefix = ref.slice(0, -1);
574
+ if (label?.startsWith(prefix) || id.startsWith(prefix) || canonicalId.startsWith(prefix)) return true;
474
575
  }
475
- return contents;
576
+ if (label?.startsWith(`${ref}:`) || id.startsWith(`${ref}:`) || canonicalId.startsWith(`${ref}:`)) return true;
577
+ return false;
476
578
  }
477
579
  /**
478
- * Resolves a relative file path with respect to a base path, handling cloud configuration appropriately.
479
- * When using a cloud configuration, the current working directory is always used instead of the context's base path.
480
- *
481
- * @param filePath - The relative or absolute file path to resolve.
482
- * @param isCloudConfig - Whether this is a cloud configuration.
483
- * @returns The resolved absolute file path.
580
+ * Checks if a provider is allowed based on a list of allowed references.
484
581
  */
485
- function getResolvedRelativePath(filePath, isCloudConfig) {
486
- if (path$1.isAbsolute(filePath) || !isCloudConfig) return filePath;
487
- return path$1.join(process.cwd(), filePath);
582
+ function isProviderAllowed(provider, allowedProviders) {
583
+ if (!Array.isArray(allowedProviders)) return true;
584
+ if (allowedProviders.length === 0) return false;
585
+ return allowedProviders.some((ref) => doesProviderRefMatch(ref, provider));
488
586
  }
489
587
  /**
490
- * Recursively loads external file references from a configuration object.
491
- *
492
- * @param config - The configuration object to process
493
- * @param context - Optional context to control file loading behavior
494
- * @returns The configuration with external file references resolved
588
+ * Detects if a provider uses OpenAI models.
589
+ * This includes direct OpenAI providers and Azure OpenAI.
495
590
  */
496
- function maybeLoadConfigFromExternalFile(config, context) {
497
- if (Array.isArray(config)) return config.map((item) => maybeLoadConfigFromExternalFile(item, context));
498
- if (typeof config === "object" && config !== null) {
499
- const result = {};
500
- for (const key of Object.keys(config)) {
501
- const childContext = key === "value" && "type" in config && typeof config.type === "string" && (config.type === "python" || config.type === "javascript") ? "assertion" : key === "vars" ? "vars" : context;
502
- result[key] = maybeLoadConfigFromExternalFile(config[key], childContext);
503
- }
504
- return result;
591
+ function isOpenAiProvider(providerId) {
592
+ const lowerProviderId = providerId.toLowerCase();
593
+ if (lowerProviderId.startsWith("openai:")) return true;
594
+ if (lowerProviderId.startsWith("azureopenai:")) return true;
595
+ if (lowerProviderId.startsWith("azure:")) {
596
+ if ([
597
+ "gpt",
598
+ "openai",
599
+ "davinci",
600
+ "curie",
601
+ "babbage",
602
+ "ada",
603
+ "text-embedding",
604
+ "whisper",
605
+ "dall-e",
606
+ "tts"
607
+ ].some((indicator) => lowerProviderId.includes(indicator))) return true;
505
608
  }
506
- return maybeLoadFromExternalFile(config, context);
609
+ return false;
507
610
  }
508
611
  /**
509
- * Parses a file path or glob pattern to extract function names and file extensions.
510
- * Function names can be specified in the filename like this:
511
- * prompt.py:myFunction or prompts.js:myFunction.
512
- * @param basePath - The base path for file resolution.
513
- * @param promptPath - The path or glob pattern.
514
- * @returns Parsed details including function name, file extension, and directory status.
612
+ * Detects if a provider uses Anthropic/Claude models.
613
+ * This includes direct Anthropic providers, Bedrock with Claude, and Vertex with Claude.
515
614
  */
516
- function parsePathOrGlob(basePath, promptPath) {
517
- if (promptPath.startsWith("file://")) promptPath = promptPath.slice(7);
518
- const filePath = path$1.resolve(basePath, promptPath);
519
- let filename = path$1.relative(basePath, filePath);
520
- let functionName;
521
- if (filename.includes(":")) {
522
- const lastColonIndex = filename.lastIndexOf(":");
523
- if (lastColonIndex > 1) {
524
- const pathWithoutFunction = filename.slice(0, lastColonIndex);
525
- if (isJavascriptFile(pathWithoutFunction) || pathWithoutFunction.endsWith(".py") || pathWithoutFunction.endsWith(".go") || pathWithoutFunction.endsWith(".rb")) {
526
- functionName = filename.slice(lastColonIndex + 1);
527
- filename = pathWithoutFunction;
528
- }
529
- }
615
+ function isAnthropicProvider(providerId) {
616
+ const lowerProviderId = providerId.toLowerCase();
617
+ if (lowerProviderId.startsWith("anthropic:")) return true;
618
+ if (lowerProviderId.startsWith("bedrock:")) {
619
+ if (lowerProviderId.includes("claude") || lowerProviderId.includes("anthropic")) return true;
530
620
  }
531
- let stats;
532
- try {
533
- stats = fs$2.statSync(path$1.join(basePath, filename));
534
- } catch (err) {
535
- if (getEnvBool("PROMPTFOO_STRICT_FILES")) throw err;
621
+ if (lowerProviderId.startsWith("vertex:")) {
622
+ if (lowerProviderId.includes("claude")) return true;
536
623
  }
537
- const normalizedFilePath = filePath.replace(/\\/g, "/");
538
- const isPathPattern = stats?.isDirectory() || hasMagic(promptPath) || hasMagic(normalizedFilePath);
539
- const safeFilename = path$1.relative(basePath, safeResolve(basePath, filename));
540
- return {
541
- extension: isPathPattern ? void 0 : path$1.parse(safeFilename).ext,
542
- filePath: path$1.join(basePath, safeFilename),
543
- functionName,
544
- isPathPattern
545
- };
624
+ return false;
546
625
  }
547
- function readOutput(outputPath) {
548
- const ext = path$1.parse(outputPath).ext.slice(1);
549
- switch (ext) {
550
- case "json": return JSON.parse(fs$2.readFileSync(outputPath, "utf-8"));
551
- default: throw new Error(`Unsupported output file format: ${ext} currently only supports json`);
626
+ const KNOWN_ENV_VARS = {
627
+ openai: "OPENAI_API_KEY",
628
+ anthropic: "ANTHROPIC_API_KEY",
629
+ google: "GOOGLE_API_KEY",
630
+ mistral: "MISTRAL_API_KEY",
631
+ cohere: "COHERE_API_KEY",
632
+ replicate: "REPLICATE_API_TOKEN",
633
+ voyage: "VOYAGE_API_KEY",
634
+ ai21: "AI21_API_KEY",
635
+ xai: "XAI_API_KEY",
636
+ groq: "GROQ_API_KEY",
637
+ deepseek: "DEEPSEEK_API_KEY",
638
+ perplexity: "PERPLEXITY_API_KEY",
639
+ hyperbolic: "HYPERBOLIC_API_KEY",
640
+ cerebras: "CEREBRAS_API_KEY",
641
+ togetherai: "TOGETHER_API_KEY",
642
+ fal: "FAL_KEY",
643
+ huggingface: "HF_TOKEN",
644
+ "cloudflare-ai": "CLOUDFLARE_API_KEY"
645
+ };
646
+ function getDefaultEnvVar(providerId) {
647
+ const prefix = providerId.split(":")[0];
648
+ return KNOWN_ENV_VARS[prefix] || `${prefix.toUpperCase()}_API_KEY`;
649
+ }
650
+ /**
651
+ * Pre-checks providers for missing API keys before evaluation starts.
652
+ * Assumes getApiKey() is side-effect free (no network calls or token refresh).
653
+ */
654
+ function checkProviderApiKeys(providers) {
655
+ const missingApiKeys = /* @__PURE__ */ new Map();
656
+ for (const provider of providers) {
657
+ const p = provider;
658
+ if (typeof p.getApiKey !== "function") continue;
659
+ if (provider.id().startsWith("azure:")) continue;
660
+ const requiresKey = typeof p.requiresApiKey === "function" ? p.requiresApiKey() : p.config?.apiKeyRequired !== false;
661
+ let apiKey;
662
+ try {
663
+ apiKey = p.getApiKey();
664
+ } catch {
665
+ apiKey = void 0;
666
+ }
667
+ if (requiresKey && !apiKey) {
668
+ const envVar = p.config?.apiKeyEnvar || getDefaultEnvVar(provider.id());
669
+ if (!missingApiKeys.has(envVar)) missingApiKeys.set(envVar, []);
670
+ missingApiKeys.get(envVar).push(provider.id());
671
+ }
552
672
  }
673
+ return missingApiKeys;
553
674
  }
554
675
  /**
555
- * Load custom Nunjucks filters from external files.
556
- * Note: If a glob pattern matches multiple files, only the last file's export is used.
557
- * Each filter name should typically resolve to a single file.
676
+ * Detects if a provider uses Google models.
677
+ * This includes direct Google/Vertex providers with Gemini and other Google models.
678
+ * Note: Vertex with Claude models is NOT counted as Google (it's Anthropic).
679
+ */
680
+ function isGoogleProvider(providerId) {
681
+ const lowerProviderId = providerId.toLowerCase();
682
+ if (lowerProviderId.startsWith("google:")) return true;
683
+ if (lowerProviderId.startsWith("vertex:")) {
684
+ if (!lowerProviderId.includes("claude")) return true;
685
+ }
686
+ return false;
687
+ }
688
+ //#endregion
689
+ //#region src/util/comparison.ts
690
+ /**
691
+ * Explicit runtime variable names that don't follow the underscore convention.
692
+ * These are added during evaluation but aren't part of the original test definition.
693
+ *
694
+ * - sessionId: Added by multi-turn strategy providers (GOAT, Crescendo)
695
+ *
696
+ * Note: Variables starting with underscore (e.g., _conversation) are automatically
697
+ * treated as runtime variables and filtered out.
698
+ */
699
+ const EXPLICIT_RUNTIME_VAR_KEYS = ["sessionId"];
700
+ /**
701
+ * Checks if a variable key is a runtime-only variable that should be filtered
702
+ * when comparing test cases.
703
+ *
704
+ * Runtime variables are identified by:
705
+ * 1. Starting with underscore (_) - convention for internal/runtime vars
706
+ * 2. Being in the explicit runtime var list (for legacy vars like sessionId)
558
707
  */
559
- async function readFilters(filters, basePath = "") {
560
- const ret = {};
561
- for (const [name, filterPath] of Object.entries(filters)) {
562
- const filePaths = globSync(path$1.join(basePath, filterPath), { windowsPathsNoEscape: true });
563
- for (const filePath of filePaths) ret[name] = await importModule(path$1.resolve(filePath));
564
- }
565
- return ret;
708
+ function isRuntimeVar(key) {
709
+ return key.startsWith("_") || EXPLICIT_RUNTIME_VAR_KEYS.includes(key);
566
710
  }
567
711
  /**
568
- * Loads configuration from an external file with variable rendering.
569
- * This is a convenience wrapper that combines renderVarsInObject and maybeLoadFromExternalFile.
570
- *
571
- * Use this for simple config fields that:
572
- * - Need variable rendering ({{ vars.x }}, {{ env.X }})
573
- * - May reference external files (file://path.json)
574
- * - Don't have nested file references that need loading
712
+ * Filters out runtime-only variables that are added during evaluation
713
+ * but aren't part of the original test definition.
575
714
  *
576
- * For fields with nested file references (like response_format.schema),
577
- * use maybeLoadResponseFormatFromExternalFile instead.
715
+ * This is used when comparing test cases to determine if a result
716
+ * corresponds to a particular test, regardless of runtime state.
578
717
  *
579
- * @param config - The configuration to process
580
- * @param vars - Variables for template rendering
581
- * @returns The processed configuration with variables rendered and files loaded
718
+ * Runtime variables are identified by:
719
+ * - Starting with underscore (e.g., _conversation, _metadata)
720
+ * - Being in the explicit list (e.g., sessionId for backward compatibility)
582
721
  */
583
- function maybeLoadFromExternalFileWithVars(config, vars) {
584
- return maybeLoadFromExternalFile(renderVarsInObject(config, vars));
722
+ function filterRuntimeVars(vars) {
723
+ if (!vars || typeof vars !== "object" || Array.isArray(vars)) return vars;
724
+ const filtered = {};
725
+ for (const [key, value] of Object.entries(vars)) if (!isRuntimeVar(key)) filtered[key] = value;
726
+ return filtered;
585
727
  }
586
728
  /**
587
- * Loads response_format configuration from an external file with variable rendering.
729
+ * Extracts only runtime variables from a vars object.
730
+ * This is the inverse of filterRuntimeVars.
588
731
  *
589
- * This function handles the special case where response_format may contain:
590
- * 1. A top-level file reference (file://format.json)
591
- * 2. A nested schema reference for json_schema type (schema: file://schema.json)
732
+ * Used to restore runtime state when re-running filtered tests.
733
+ */
734
+ function extractRuntimeVars(vars) {
735
+ if (!vars || typeof vars !== "object" || Array.isArray(vars)) return;
736
+ const extracted = {};
737
+ for (const [key, value] of Object.entries(vars)) if (isRuntimeVar(key)) extracted[key] = value;
738
+ return Object.keys(extracted).length > 0 ? extracted : void 0;
739
+ }
740
+ function varsMatch(vars1, vars2) {
741
+ return deepEqual(vars1, vars2);
742
+ }
743
+ /**
744
+ * Generate a unique key for a test case for deduplication purposes.
745
+ * Excludes runtime variables and includes strategyId to distinguish tests
746
+ * with the same prompt but different strategies.
592
747
  *
593
- * Both levels need variable rendering and file loading.
748
+ * @param testCase - The test case to generate a key for
749
+ * @returns A JSON string that uniquely identifies the test case
750
+ */
751
+ function getTestCaseDeduplicationKey(testCase) {
752
+ const filteredVars = filterRuntimeVars(testCase.vars);
753
+ const strategyId = testCase.metadata?.strategyId || "none";
754
+ return JSON.stringify({
755
+ vars: filteredVars,
756
+ strategyId
757
+ });
758
+ }
759
+ /**
760
+ * Deduplicates an array of test cases based on their vars and strategyId.
761
+ * Tests with the same vars but different strategies are considered different.
762
+ * Runtime variables (like _conversation, sessionId) are filtered out before comparison.
594
763
  *
595
- * @param responseFormat - The response_format configuration
596
- * @param vars - Variables for template rendering
597
- * @returns The processed response_format with all files loaded
764
+ * @param tests - Array of test cases to deduplicate
765
+ * @returns Deduplicated array of test cases
598
766
  */
599
- function maybeLoadResponseFormatFromExternalFile(responseFormat, vars) {
600
- if (responseFormat === void 0 || responseFormat === null) return responseFormat;
601
- const loaded = maybeLoadFromExternalFile(renderVarsInObject(responseFormat, vars));
602
- if (!loaded || typeof loaded !== "object") return loaded;
603
- if (loaded.type === "json_schema") {
604
- const nestedSchema = loaded.schema || loaded.json_schema?.schema;
605
- if (nestedSchema) {
606
- const loadedSchema = maybeLoadFromExternalFile(renderVarsInObject(nestedSchema, vars));
607
- if (loaded.schema !== void 0) return {
608
- ...loaded,
609
- schema: loadedSchema
610
- };
611
- else if (loaded.json_schema?.schema !== void 0) return {
612
- ...loaded,
613
- json_schema: {
614
- ...loaded.json_schema,
615
- schema: loadedSchema
616
- }
617
- };
618
- }
767
+ function deduplicateTestCases(tests) {
768
+ const seen = /* @__PURE__ */ new Set();
769
+ return tests.filter((test) => {
770
+ const key = getTestCaseDeduplicationKey(test);
771
+ if (seen.has(key)) return false;
772
+ seen.add(key);
773
+ return true;
774
+ });
775
+ }
776
+ function resultIsForTestCase(result, testCase) {
777
+ const testProviderId = testCase.provider ? providerToIdentifier(testCase.provider) : void 0;
778
+ const resultProviderId = providerToIdentifier(result.provider);
779
+ const providersMatch = !testProviderId || !resultProviderId || testProviderId === resultProviderId;
780
+ const resultVars = filterRuntimeVars(result.vars);
781
+ const testVars = filterRuntimeVars(testCase.vars);
782
+ const doVarsMatch = varsMatch(testVars, resultVars);
783
+ const isMatch = doVarsMatch && providersMatch;
784
+ if (!isMatch) {
785
+ const varKeys = testVars ? Object.keys(testVars).join(", ") : "none";
786
+ logger.debug(`[resultIsForTestCase] No match: vars=${doVarsMatch}, providers=${providersMatch}`, {
787
+ testProvider: testProviderId || "none",
788
+ resultProvider: resultProviderId || "none",
789
+ testVarKeys: varKeys
790
+ });
619
791
  }
620
- return loaded;
792
+ return isMatch;
621
793
  }
794
+ //#endregion
795
+ //#region src/util/env.ts
622
796
  /**
623
- * Renders variables in a tools object and loads from external file if applicable.
624
- * This function combines renderVarsInObject and maybeLoadFromExternalFile into a single step
625
- * specifically for handling tools configurations.
626
- *
627
- * Supports loading from JSON, YAML, Python, and JavaScript files.
628
- *
629
- * @param tools - The tools configuration object or array to process.
630
- * @param vars - Variables to use for rendering.
631
- * @returns The processed tools configuration with variables rendered and content loaded from files if needed.
632
- * @throws {Error} If the loaded tools are in an invalid format
797
+ * Load environment variables from .env file(s).
798
+ * @param envPath - Single path, array of paths, or undefined for default .env loading.
799
+ * When paths are explicitly specified, all files must exist or an error is thrown.
800
+ * When multiple files are provided, later files override values from earlier files.
633
801
  */
634
- async function maybeLoadToolsFromExternalFile(tools, vars) {
635
- const rendered = renderVarsInObject(tools, vars);
636
- if (typeof rendered === "string" && rendered.startsWith("file://")) {
637
- const { filePath, functionName } = parseFileUrl(rendered);
638
- if (functionName && (filePath.endsWith(".py") || isJavascriptFile(filePath))) {
639
- const fileType = filePath.endsWith(".py") ? "Python" : "JavaScript";
640
- logger.debug(`[maybeLoadToolsFromExternalFile] Loading tools from ${fileType} file: ${filePath}:${functionName}`);
641
- try {
642
- let toolDefinitions;
643
- if (filePath.endsWith(".py")) {
644
- const absPath = safeResolve(state.basePath || process.cwd(), filePath);
645
- logger.debug(`[maybeLoadToolsFromExternalFile] Resolved Python path: ${absPath}`);
646
- toolDefinitions = await runPython(absPath, functionName, []);
647
- } else {
648
- const absPath = safeResolve(state.basePath || process.cwd(), filePath);
649
- logger.debug(`[maybeLoadToolsFromExternalFile] Resolved JavaScript path: ${absPath}`);
650
- const module = await importModule(absPath);
651
- const fn = module[functionName] || module.default?.[functionName];
652
- if (typeof fn !== "function") {
653
- const availableExports = Object.keys(module).filter((k) => k !== "default");
654
- const basePath = state.basePath || process.cwd();
655
- throw new Error(`Function "${functionName}" not found in ${filePath}. Available exports: ${availableExports.length > 0 ? availableExports.join(", ") : "(none)"}\nResolved from: ${basePath}`);
656
- }
657
- toolDefinitions = await Promise.resolve(fn());
658
- }
659
- if (!toolDefinitions || typeof toolDefinitions === "string" || typeof toolDefinitions === "number" || typeof toolDefinitions === "boolean") throw new Error(`Function "${functionName}" must return an array or object of tool definitions, but returned: ${toolDefinitions === null ? "null" : typeof toolDefinitions}`);
660
- logger.debug(`[maybeLoadToolsFromExternalFile] Successfully loaded ${Array.isArray(toolDefinitions) ? toolDefinitions.length : "object"} tools`);
661
- return toolDefinitions;
662
- } catch (err) {
663
- const errorMessage = err instanceof Error ? err.message : String(err);
664
- const basePath = state.basePath || process.cwd();
665
- throw new Error(`Failed to load tools from ${rendered}:\n${errorMessage}\n\nMake sure the function "${functionName}" exists and returns a valid tool definition array.\nResolved from: ${basePath}`);
666
- }
667
- }
668
- if (filePath.endsWith(".py") || isJavascriptFile(filePath)) {
669
- const ext = filePath.endsWith(".py") ? "Python" : "JavaScript";
670
- const basePath = state.basePath || process.cwd();
671
- throw new Error(`Cannot load tools from ${rendered}\n${ext} files require a function name. Use this format:\n tools: file://${filePath}:get_tools\n\nYour ${ext} file should export a function that returns tool definitions:\n` + (filePath.endsWith(".py") ? ` def get_tools():\n return [{"type": "function", "function": {...}}]` : ` module.exports.get_tools = () => [{ type: "function", function: {...} }];`) + `\n\nResolved from: ${basePath}`);
802
+ function setupEnv(envPath) {
803
+ if (envPath) {
804
+ const paths = (Array.isArray(envPath) ? envPath : [envPath]).flatMap((p) => p.includes(",") ? p.split(",").map((s) => s.trim()) : p.trim()).filter((p) => p.length > 0);
805
+ if (paths.length === 0) {
806
+ dotenv.config({ quiet: true });
807
+ return;
672
808
  }
673
- }
674
- if (Array.isArray(rendered)) {
675
- const results = await Promise.all(rendered.map((item) => maybeLoadToolsFromExternalFile(item, vars)));
676
- if (results.every((r) => Array.isArray(r))) return results.flat();
677
- return results;
678
- }
679
- if (typeof rendered !== "string") return rendered;
680
- const loaded = maybeLoadFromExternalFile(rendered);
681
- if (loaded !== void 0 && loaded !== null && typeof loaded === "string") {
682
- if (loaded.startsWith("file://")) throw new Error(`Failed to load tools from ${loaded}\nEnsure the file exists and contains valid JSON or YAML tool definitions.`);
683
- if (loaded.includes("def ") || loaded.includes("import ")) throw new Error("Invalid tools configuration: file appears to contain Python code.\nPython files require a function name. Use this format:\n tools: file://tools.py:get_tools");
684
- throw new Error("Invalid tools configuration: expected an array or object, but got a string.\nIf using file://, ensure the file contains valid JSON or YAML tool definitions.");
685
- }
686
- return loaded;
809
+ for (const p of paths) if (!fs$2.existsSync(p)) throw new Error(`Environment file not found: ${p}`);
810
+ if (paths.length === 1) logger.info(`Loading environment variables from ${paths[0]}`);
811
+ else logger.info(`Loading environment variables from: ${paths.join(", ")}`);
812
+ const pathArg = paths.length === 1 ? paths[0] : paths;
813
+ dotenv.config({
814
+ path: pathArg,
815
+ override: true,
816
+ quiet: true
817
+ });
818
+ } else dotenv.config({ quiet: true });
687
819
  }
688
820
  //#endregion
689
821
  //#region src/googleSheets.ts
@@ -806,6 +938,15 @@ async function writeCsvToGoogleSheet(rows, url) {
806
938
  //#endregion
807
939
  //#region src/server/utils/evalTableUtils.ts
808
940
  /**
941
+ * Error thrown when a comparison eval is not found.
942
+ */
943
+ var ComparisonEvalNotFoundError = class extends Error {
944
+ constructor(evalId) {
945
+ super(`Comparison eval not found: ${evalId}`);
946
+ this.name = "ComparisonEvalNotFoundError";
947
+ }
948
+ };
949
+ /**
809
950
  *
810
951
  *
811
952
  *
@@ -919,6 +1060,150 @@ function tableRowToCsvValues(row, options = {}) {
919
1060
  return rowValues;
920
1061
  }
921
1062
  /**
1063
+ * Generates CSV data from evaluation table data.
1064
+ *
1065
+ * Column structure per prompt:
1066
+ * - Output: Pure LLM output text (no pass/fail prefix)
1067
+ * - Status: PASS | FAIL | ERROR
1068
+ * - Score: Numeric score (e.g., "1.00")
1069
+ * - Named Scores: JSON object with per-assertion scores (e.g., {"clarity": 0.90, "accuracy": 0.85})
1070
+ * - Grader Reason: Explanation from the grader
1071
+ * - Comment: Additional grader comment
1072
+ *
1073
+ * This function is the single source of truth for CSV generation,
1074
+ * used by both the WebUI export and CLI export.
1075
+ *
1076
+ * @param table - The evaluation table data
1077
+ * @param options - Export options
1078
+ * @returns CSV formatted string
1079
+ */
1080
+ function evalTableToCsv(table, options = { isRedteam: false }) {
1081
+ const { isRedteam } = options;
1082
+ const hasDescriptions = table.body.some((row) => row.test.description);
1083
+ return stringify([buildCsvHeaders(table.head.vars, table.head.prompts, {
1084
+ hasDescriptions,
1085
+ isRedteam
1086
+ }), ...table.body.map((row) => tableRowToCsvValues(row, {
1087
+ hasDescriptions,
1088
+ isRedteam
1089
+ }))]);
1090
+ }
1091
+ /**
1092
+ * Generate JSON data from evaluation table
1093
+ * @param table Evaluation table data
1094
+ * @returns JSON object
1095
+ */
1096
+ function evalTableToJson(table) {
1097
+ return table;
1098
+ }
1099
+ function getEvalTableOutputPromptLocationsBySize(payload) {
1100
+ return payload.table.body.flatMap((row, rowIndex) => row.outputs.flatMap((output, outputIndex) => output?.prompt ? [{
1101
+ rowIndex,
1102
+ outputIndex,
1103
+ length: output.prompt.length
1104
+ }] : [])).sort((a, b) => b.length - a.length);
1105
+ }
1106
+ function stripEvalTableOutputPrompts(payload, locationsToStrip) {
1107
+ return {
1108
+ ...payload,
1109
+ table: {
1110
+ ...payload.table,
1111
+ body: payload.table.body.map((row, rowIndex) => ({
1112
+ ...row,
1113
+ outputs: row.outputs.map((output, outputIndex) => {
1114
+ if (!output || !locationsToStrip.has(`${rowIndex}:${outputIndex}`)) return output;
1115
+ return {
1116
+ ...output,
1117
+ prompt: ""
1118
+ };
1119
+ })
1120
+ }))
1121
+ }
1122
+ };
1123
+ }
1124
+ function getEvalTablePromptStrippedPayload(payload, promptLocations, promptCountToStrip) {
1125
+ return stripEvalTableOutputPrompts(payload, new Set(promptLocations.slice(0, promptCountToStrip).map(({ rowIndex, outputIndex }) => `${rowIndex}:${outputIndex}`)));
1126
+ }
1127
+ /**
1128
+ * Merges comparison tables with the main table for side-by-side CSV export.
1129
+ *
1130
+ * @param mainEvalId - The ID of the main evaluation
1131
+ * @param mainTable - The main evaluation table
1132
+ * @param comparisonData - Array of comparison eval data (eval ID and table)
1133
+ * @returns Merged table with all prompts and outputs combined
1134
+ */
1135
+ function mergeComparisonTables(mainEvalId, mainTable, comparisonData) {
1136
+ return {
1137
+ head: {
1138
+ prompts: [...mainTable.head.prompts.map((prompt) => ({
1139
+ ...prompt,
1140
+ label: `[${mainEvalId}] ${prompt.label || ""}`
1141
+ })), ...comparisonData.flatMap(({ evalId, table }) => table.head.prompts.map((prompt) => ({
1142
+ ...prompt,
1143
+ label: `[${evalId}] ${prompt.label || ""}`
1144
+ })))],
1145
+ vars: mainTable.head.vars
1146
+ },
1147
+ body: mainTable.body.map((row) => {
1148
+ const testIdx = row.testIdx;
1149
+ const matchingRows = comparisonData.map(({ table }) => table.body.find((compRow) => compRow.testIdx === testIdx)).filter((r) => r !== void 0);
1150
+ return {
1151
+ ...row,
1152
+ outputs: [...row.outputs, ...matchingRows.flatMap((r) => r.outputs)]
1153
+ };
1154
+ })
1155
+ };
1156
+ }
1157
+ /**
1158
+ * High-level function to generate CSV from an evaluation.
1159
+ *
1160
+ * Used by WebUI for CSV downloads (with or without comparison evals).
1161
+ * For CLI exports, use `streamEvalCsv` which is more memory-efficient
1162
+ * for large datasets.
1163
+ *
1164
+ * Both functions use the same underlying formatting (`evalTableToCsv`,
1165
+ * `buildCsvHeaders`, `tableRowToCsvValues`) to ensure consistent output.
1166
+ *
1167
+ * @param eval_ - The evaluation to export
1168
+ * @param options - Export options including filters and comparison eval IDs
1169
+ * @returns CSV formatted string
1170
+ * @throws ComparisonEvalNotFoundError if a comparison eval ID is not found
1171
+ * @throws Error if comparison exports requested without findEvalById callback
1172
+ */
1173
+ async function generateEvalCsv(eval_, options = {}) {
1174
+ const UNLIMITED_RESULTS = Number.MAX_SAFE_INTEGER;
1175
+ const mainTable = await eval_.getTablePage({
1176
+ offset: 0,
1177
+ limit: UNLIMITED_RESULTS,
1178
+ filterMode: options.filterMode,
1179
+ searchQuery: options.searchQuery,
1180
+ filters: options.filters
1181
+ });
1182
+ let finalTable = mainTable;
1183
+ if (options.comparisonEvalIds && options.comparisonEvalIds.length > 0) {
1184
+ if (!options.findEvalById) throw new Error("findEvalById callback is required for comparison exports. Pass Eval.findById when calling from server routes.");
1185
+ const indices = mainTable.body.map((row) => row.testIdx);
1186
+ const comparisonData = await Promise.all(options.comparisonEvalIds.map(async (comparisonEvalId) => {
1187
+ const comparisonEval = await options.findEvalById(comparisonEvalId);
1188
+ if (!comparisonEval) throw new ComparisonEvalNotFoundError(comparisonEvalId);
1189
+ const table = await comparisonEval.getTablePage({
1190
+ offset: 0,
1191
+ limit: indices.length,
1192
+ filterMode: "all",
1193
+ testIndices: indices,
1194
+ searchQuery: options.searchQuery,
1195
+ filters: options.filters
1196
+ });
1197
+ return {
1198
+ evalId: comparisonEval.id,
1199
+ table
1200
+ };
1201
+ }));
1202
+ finalTable = mergeComparisonTables(eval_.id, mainTable, comparisonData);
1203
+ }
1204
+ return evalTableToCsv(finalTable, { isRedteam: Boolean(eval_.config.redteam) });
1205
+ }
1206
+ /**
922
1207
  * Stream CSV data from an evaluation in batches.
923
1208
  *
924
1209
  * This is more memory-efficient for large evaluations as it processes
@@ -1038,7 +1323,7 @@ function createOutputMetadata(evalRecord) {
1038
1323
  arch: os$1.arch(),
1039
1324
  exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
1040
1325
  evaluationCreatedAt,
1041
- author: evalRecord.author
1326
+ author: evalRecord.author ?? void 0
1042
1327
  };
1043
1328
  }
1044
1329
  /**
@@ -1173,6 +1458,6 @@ function printBorder() {
1173
1458
  logger.info(border);
1174
1459
  }
1175
1460
  //#endregion
1176
- export { checkProviderApiKeys as C, isGoogleProvider as D, isAnthropicProvider as E, isOpenAiProvider as O, resultIsForTestCase as S, getProviderDescription as T, setupEnv as _, getNunjucksEngineForFilePath as a, filterRuntimeVars as b, maybeLoadFromExternalFile as c, maybeLoadToolsFromExternalFile as d, parsePathOrGlob as f, parseFileUrl as g, loadFunction as h, fetchCsvFromGoogleSheet as i, isProviderAllowed as k, maybeLoadFromExternalFileWithVars as l, readOutput as m, writeMultipleOutputs as n, getResolvedRelativePath as o, readFilters as p, writeOutput as r, maybeLoadConfigFromExternalFile as s, printBorder as t, maybeLoadResponseFormatFromExternalFile as u, deduplicateTestCases as v, doesProviderRefMatch as w, getTestCaseDeduplicationKey as x, extractRuntimeVars as y };
1461
+ export { maybeLoadConfigFromExternalFile as A, isProviderAllowed as C, readProviderConfigFile as D, normalizeProviderRef as E, parsePathOrGlob as F, readFilters as I, readOutput as L, maybeLoadFromExternalFileWithVars as M, maybeLoadResponseFormatFromExternalFile as N, getNunjucksEngineForFilePath as O, maybeLoadToolsFromExternalFile as P, loadFunction as R, isOpenAiProvider as S, loadProviderConfigsFromFile as T, checkProviderApiKeys as _, evalTableToJson as a, isAnthropicProvider as b, getEvalTablePromptStrippedPayload as c, setupEnv as d, deduplicateTestCases as f, resultIsForTestCase as g, getTestCaseDeduplicationKey as h, ComparisonEvalNotFoundError as i, maybeLoadFromExternalFile as j, getResolvedRelativePath as k, mergeComparisonTables as l, filterRuntimeVars as m, writeMultipleOutputs as n, generateEvalCsv as o, extractRuntimeVars as p, writeOutput as r, getEvalTableOutputPromptLocationsBySize as s, printBorder as t, fetchCsvFromGoogleSheet as u, doesProviderRefMatch as v, isProviderConfigFileReference as w, isGoogleProvider as x, getProviderDescription as y, parseFileUrl as z };
1177
1462
 
1178
- //# sourceMappingURL=util-oGMLA7vc.js.map
1463
+ //# sourceMappingURL=util-I-Rf-KaD.js.map