promptfoo 0.121.4 → 0.121.5

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 (346) hide show
  1. package/dist/src/{ListApp-DQkFNqE9.js → ListApp-BRUsT43Y.js} +1 -1
  2. package/dist/src/{accounts-Dy17bs4D.cjs → accounts-BIFntVWB.cjs} +4 -4
  3. package/dist/src/{accounts-F9d_5sMC.js → accounts-CLJHCDDb.js} +6 -6
  4. package/dist/src/{accounts-DhMYUUbu.js → accounts-CaLNYnf7.js} +4 -4
  5. package/dist/src/{accounts-DdJ2pHMI.js → accounts-bnyHT7Ju.js} +5 -5
  6. package/dist/src/{agentic-utils-w68v6_Dz.js → agentic-utils-B5krlibj.js} +3 -3
  7. package/dist/src/{agentic-utils-P172hM8B.js → agentic-utils-Ba67xmgs.js} +2 -2
  8. package/dist/src/{agentic-utils-qFlm6zes.js → agentic-utils-BclbiXiq.js} +3 -3
  9. package/dist/src/{agentic-utils-BpX5b23w.cjs → agentic-utils-D2x0wGhB.cjs} +2 -2
  10. package/dist/src/{agents-CgaMXvLM.js → agents-BGqaTDnr.js} +5 -5
  11. package/dist/src/{agents-8FDnTriG.js → agents-BV9yFpXX.js} +5 -5
  12. package/dist/src/{agents-aYPQLf8W.js → agents-BYdMl1UE.js} +4 -4
  13. package/dist/src/{agents-pQeBEXMm.js → agents-DhxWMCtH.js} +5 -5
  14. package/dist/src/{agents-D7-HGxUj.cjs → agents-DiWmQYH9.cjs} +4 -4
  15. package/dist/src/{agents-BahDpe5G.cjs → agents-WULPVjbH.cjs} +4 -4
  16. package/dist/src/{agents-DJ35I3Nt.js → agents-emVcx3yh.js} +5 -5
  17. package/dist/src/{agents-C-R_jfzI.js → agents-n6vPqV3i.js} +4 -4
  18. package/dist/src/{aimlapi-BCq3MHeL.js → aimlapi-BxqK9HF_.js} +7 -7
  19. package/dist/src/{aimlapi-qcK4OT55.cjs → aimlapi-BzLjZI_m.cjs} +6 -6
  20. package/dist/src/{aimlapi-BD6J9oKt.js → aimlapi-DR4pgeiC.js} +6 -6
  21. package/dist/src/{aimlapi-sgYnkE54.js → aimlapi-uPGp0Zdo.js} +7 -7
  22. package/dist/src/app/app/tsconfig.app.tsbuildinfo +1 -1
  23. package/dist/src/app/assets/Report-vjzrbgce.js +1 -0
  24. package/dist/src/app/assets/index-B3NQ8HTd.js +385 -0
  25. package/dist/src/app/assets/{index-BXGkeMwh.css → index-Cli2yAXv.css} +1 -1
  26. package/dist/src/app/index.html +27 -2
  27. package/dist/src/{audio-DcVKoInv.js → audio-BvpTOArF.js} +4 -4
  28. package/dist/src/{audio-BQtNuYBj.cjs → audio-C0vDeS0j.cjs} +3 -3
  29. package/dist/src/{audio-B7izf48x.js → audio-CScmnmEB.js} +4 -4
  30. package/dist/src/{audio-COrn8rM6.js → audio-Da8U9IS5.js} +3 -3
  31. package/dist/src/{base-fZ9wgg50.js → base-BOMaNEes.js} +3 -3
  32. package/dist/src/{base-PYJvBE1i.js → base-BTux96b1.js} +2 -2
  33. package/dist/src/{base-D-670DX8.cjs → base-Tw6uhH8K.cjs} +2 -2
  34. package/dist/src/{base-yrI1Yal4.js → base-dYsl2hmL.js} +3 -3
  35. package/dist/src/{blobs-D2FAd1Q5.cjs → blobs-B95F_7vE.cjs} +2 -2
  36. package/dist/src/{blobs-C-F78Kfn.js → blobs-BW4U31ue.js} +2 -2
  37. package/dist/src/{blobs-BCZavS8s.js → blobs-D_gg8nbm.js} +3 -3
  38. package/dist/src/{blobs-BQWqnnvL.js → blobs-DjLby-uP.js} +3 -3
  39. package/dist/src/{cache-mb7c8hbp.js → cache-BI5BY7ey.js} +4 -4
  40. package/dist/src/{cache-DbLsVWB2.cjs → cache-BRkhlH3k.cjs} +1 -1
  41. package/dist/src/cache-BlC6aeJ0.js +3 -0
  42. package/dist/src/{cache-D5NZmMiT.js → cache-Bzttsk0X.js} +2 -2
  43. package/dist/src/{cache-C4Xb-hNb.js → cache-Cr-qWIbP.js} +3 -3
  44. package/dist/src/{cache-BIyPcp5v.cjs → cache-DGg-yTZG.cjs} +2 -2
  45. package/dist/src/{chat-Dr3DUQ0D.js → chat-BLOdH60v.js} +12 -12
  46. package/dist/src/{chat-BfPaS15_.js → chat-Cx_LkwvZ.js} +12 -12
  47. package/dist/src/{chat-mW0ORo8G.js → chat-D9nudO9b.js} +4 -4
  48. package/dist/src/{chat-I9izLm49.js → chat-DChSH_Es.js} +12 -12
  49. package/dist/src/{chat-MKxMnZJZ.js → chat-DG2LkwLq.js} +2 -2
  50. package/dist/src/{chat-BPXSW8Bv.cjs → chat-DH97tVV9.cjs} +2 -2
  51. package/dist/src/{chat-0bwXjVP0.js → chat-aMQZw6R7.js} +4 -4
  52. package/dist/src/{chat-CclRbxGf.cjs → chat-vYqqv1gP.cjs} +11 -11
  53. package/dist/src/{chatkit-zUIVoDos.js → chatkit-B8X34dQc.js} +4 -4
  54. package/dist/src/{chatkit-Cv6AhukM.js → chatkit-BXu42Qwt.js} +3 -3
  55. package/dist/src/{chatkit-CJnHRRMM.js → chatkit-CbMRoeYw.js} +4 -4
  56. package/dist/src/{chatkit-BoWoSgXl.cjs → chatkit-D44VyUyB.cjs} +3 -3
  57. package/dist/src/{claude-agent-sdk-CPJo3dBQ.cjs → claude-agent-sdk-BRq0bbIK.cjs} +8 -8
  58. package/dist/src/{claude-agent-sdk-BQNuLaAK.js → claude-agent-sdk-BjriSVRZ.js} +7 -7
  59. package/dist/src/{claude-agent-sdk-Dtq_L-Sc.js → claude-agent-sdk-BzNZeZ0N.js} +7 -7
  60. package/dist/src/{claude-agent-sdk-nfAIcxNf.js → claude-agent-sdk-DYv_AJ8u.js} +7 -7
  61. package/dist/src/cloud-CoD5OacT.js +3 -0
  62. package/dist/src/{cloud-DQZ5sVjW.js → cloud-Da0bofJd.js} +3 -3
  63. package/dist/src/{cloudflare-ai-BIB567w6.js → cloudflare-ai-CXC4b1EU.js} +4 -4
  64. package/dist/src/{cloudflare-ai-DlKr0rY7.js → cloudflare-ai-CyBoIs1Q.js} +6 -6
  65. package/dist/src/{cloudflare-ai-DGLte7Py.js → cloudflare-ai-DGOwgexC.js} +6 -6
  66. package/dist/src/{cloudflare-ai-Dl3N9OVD.cjs → cloudflare-ai-DJv5qnyb.cjs} +4 -4
  67. package/dist/src/{cloudflare-gateway-BDZrYydE.js → cloudflare-gateway-1sAoOyft.js} +5 -5
  68. package/dist/src/{cloudflare-gateway-CiIZHU0Q.js → cloudflare-gateway-D-dnkzCF.js} +5 -5
  69. package/dist/src/{cloudflare-gateway-BYDp495F.cjs → cloudflare-gateway-DKVjkDav.cjs} +3 -3
  70. package/dist/src/{cloudflare-gateway-DI1HNP5F.js → cloudflare-gateway-TJkVrZlB.js} +3 -3
  71. package/dist/src/codex-app-server-CCLjqCh9.js +1915 -0
  72. package/dist/src/codex-app-server-CCe0TiDc.js +1915 -0
  73. package/dist/src/codex-app-server-CPW1LFwh.js +1916 -0
  74. package/dist/src/codex-app-server-VMRnjZ68.cjs +1920 -0
  75. package/dist/src/codex-sdk-1jm_qPHf.js +3 -0
  76. package/dist/src/{codex-sdk-C2_M2pl_.cjs → codex-sdk-Bd8UbO9q.cjs} +5 -5
  77. package/dist/src/{codex-sdk-CpqiOqDO.js → codex-sdk-BgEFQ70r.js} +6 -6
  78. package/dist/src/{codex-sdk-Rtky3M4I.js → codex-sdk-Bzb_TqX9.js} +6 -6
  79. package/dist/src/{codex-sdk-CWEnH70W.cjs → codex-sdk-Danroptg.cjs} +1 -1
  80. package/dist/src/{codex-sdk-CErXn7qh.js → codex-sdk-DfvDTN33.js} +5 -5
  81. package/dist/src/{cometapi-CtJ-mS8R.js → cometapi-B5ImDlSm.js} +8 -8
  82. package/dist/src/{cometapi-UVOryo4W.cjs → cometapi-BgAkuYCw.cjs} +7 -7
  83. package/dist/src/{cometapi-BUlt_ELa.js → cometapi-CC7hWxmX.js} +8 -8
  84. package/dist/src/{cometapi-DT-jlVCB.js → cometapi-CCbpHkuF.js} +7 -7
  85. package/dist/src/{completion-x0a_c2y1.js → completion-2iuYVxwi.js} +6 -6
  86. package/dist/src/{completion-Dnxn7E-j.js → completion-CrD6MQ93.js} +5 -5
  87. package/dist/src/{completion-BozdoXba.cjs → completion-DtQ72Bm3.cjs} +5 -5
  88. package/dist/src/{completion-HUe8wDhZ.js → completion-Vq_ad618.js} +6 -6
  89. package/dist/src/{createHash-ChI45QR1.js → createHash-DPpsZgFF.js} +1 -1
  90. package/dist/src/{createHash-CwDVU5xr.js → createHash-Un4Q_huE.js} +1 -1
  91. package/dist/src/{createHash-B7KvgoOD.cjs → createHash-VvBIc-AW.cjs} +1 -1
  92. package/dist/src/{docker-DCgsveLD.js → docker--3qzPa-6.js} +6 -6
  93. package/dist/src/{docker-DS4_Osau.cjs → docker-D3AY-5F5.cjs} +5 -5
  94. package/dist/src/{docker-CQmlA2NU.js → docker-DCsCDvwM.js} +6 -6
  95. package/dist/src/{docker-ClnmCf1Z.js → docker-Dorv4_Dg.js} +5 -5
  96. package/dist/src/{embedding-I45KG3o7.cjs → embedding-BXhN5lCH.cjs} +5 -5
  97. package/dist/src/{embedding-nFbumxcv.js → embedding-ChS1ivFS.js} +5 -5
  98. package/dist/src/{embedding-D3xTseo7.js → embedding-DNRvZwRN.js} +6 -6
  99. package/dist/src/{embedding-DD9wa3ae.js → embedding-D_bI4NDq.js} +6 -6
  100. package/dist/src/{errors-Cw810C93.js → errors-DFHe4L-n.js} +1 -1
  101. package/dist/src/{esm-Dh4dOLlt.js → esm-B6whoAcf.js} +2 -2
  102. package/dist/src/{esm-C7PnfdF8.js → esm-BRkfNsYs.js} +1 -1
  103. package/dist/src/{esm-tVgYPY-f.js → esm-BX8fwlAO.js} +2 -2
  104. package/dist/src/{esm-CtEPLdAj.cjs → esm-B_rGuPTo.cjs} +1 -1
  105. package/dist/src/{eval-CzJFfFO9.js → eval-BQPLBJbw.js} +1 -1
  106. package/dist/src/{eval-u4UVafl6.js → eval-DJ_4A-tr.js} +14 -14
  107. package/dist/src/evalResult-BBJAHAtw.cjs +2 -0
  108. package/dist/src/evalResult-BBK58h2B.js +3 -0
  109. package/dist/src/{evalResult-KZqXl4XP.cjs → evalResult-Cx-8OWkb.cjs} +28 -10
  110. package/dist/src/{evalResult-D3hVYFis.js → evalResult-D6P5I5il.js} +29 -11
  111. package/dist/src/{evalResult-Bgm9ZH31.js → evalResult-pSvGWFMo.js} +29 -11
  112. package/dist/src/{evaluator-IvuDYSvQ.js → evaluator-D-UIbbYq.js} +845 -98
  113. package/dist/src/evaluator-DgLKaZk8.js +3 -0
  114. package/dist/src/{extractor-Dk6bRWkv.js → extractor-BM3jRERL.js} +5 -5
  115. package/dist/src/{extractor-WVPOrH43.cjs → extractor-Dxr2J_wK.cjs} +5 -5
  116. package/dist/src/{extractor-DNSeBVOJ.js → extractor-DxyiFhPk.js} +6 -6
  117. package/dist/src/{extractor-CAfTSraf.js → extractor-YlZbUMsL.js} +6 -6
  118. package/dist/src/fetch-8viavNv8.js +3 -0
  119. package/dist/src/{fetch-BEWnXrrG.js → fetch-B6ch2nU2.js} +9 -20
  120. package/dist/src/{fetch-Di00EQrc.js → fetch-D9xxyC1p.js} +221 -232
  121. package/dist/src/{fetch-CJU5ELPa.cjs → fetch-NuqXW1Xb.cjs} +221 -244
  122. package/dist/src/{fetch-B0Z3Oe4k.js → fetch-Y5qX_kST.js} +8 -19
  123. package/dist/src/{fileExtensions-BArZuxsI.js → fileExtensions-8CjoL7vB.js} +1 -1
  124. package/dist/src/{fileExtensions-DnqA1y9x.js → fileExtensions-BGh-W-HT.js} +1 -1
  125. package/dist/src/{fileExtensions-bYh77CN8.cjs → fileExtensions-D9h-8Wxg.cjs} +1 -1
  126. package/dist/src/{fileExtensions-AWa2ZML4.js → fileExtensions-DysCsxNG.js} +1 -1
  127. package/dist/src/{formatDuration-DZzPsexs.js → formatDuration-Ch4A7G3o.js} +1 -1
  128. package/dist/src/{genaiTracer-yRuxj9-L.cjs → genaiTracer-BokHC-MW.cjs} +1 -1
  129. package/dist/src/{genaiTracer-DWdZ28hY.js → genaiTracer-C3ZPQU60.js} +1 -1
  130. package/dist/src/{genaiTracer-XnrcgDCe.js → genaiTracer-CFny3gOy.js} +1 -1
  131. package/dist/src/{genaiTracer-COYDi-tC.js → genaiTracer-DxODqT9e.js} +1 -1
  132. package/dist/src/{graders-Zy3x0zqX.js → graders-BoUqsCEm.js} +1303 -2044
  133. package/dist/src/{graders--zknU_uk.cjs → graders-Bw1wk_21.cjs} +1553 -2240
  134. package/dist/src/graders-C84JI-m5.js +2 -0
  135. package/dist/src/graders-CBbd0K0Q.cjs +2 -0
  136. package/dist/src/graders-CbQqpHSN.js +3 -0
  137. package/dist/src/{graders-eIHhRqoC.js → graders-CgPn32yp.js} +1300 -2041
  138. package/dist/src/{graders-pvbReLLn.js → graders-CwrbifOo.js} +747 -1488
  139. package/dist/src/graders-DS42d3ZG.js +2 -0
  140. package/dist/src/{image-9302QVqR.js → image-BeWaInPF.js} +3 -3
  141. package/dist/src/{image-DVz2RiMF.js → image-BmilRNqO.js} +7 -7
  142. package/dist/src/{image-x6KqLQl4.cjs → image-CxJoa3aW.cjs} +6 -6
  143. package/dist/src/{image-De2FBmYV.cjs → image-D10dNAav.cjs} +3 -3
  144. package/dist/src/{image-dnoUgPrC.js → image-Dr_3I3nK.js} +4 -4
  145. package/dist/src/{image-B5Mv-Z3h.js → image-DsGRlkh7.js} +7 -7
  146. package/dist/src/{image-qUpPvmNZ.js → image-a_SGUobh.js} +6 -6
  147. package/dist/src/{image-u7-rKnYU.js → image-qjO6FWPs.js} +4 -4
  148. package/dist/src/index.cjs +1052 -296
  149. package/dist/src/index.d.cts +124 -13
  150. package/dist/src/index.d.ts +125 -14
  151. package/dist/src/index.js +1018 -262
  152. package/dist/src/{interactiveCheck-CLERUB0c.js → interactiveCheck-CCICw2cy.js} +2 -2
  153. package/dist/src/{invariant-BtWWVVhl.js → invariant-B2Rf6avk.js} +1 -1
  154. package/dist/src/{invariant-vgHWClmd.js → invariant-DIYf9sP1.js} +1 -1
  155. package/dist/src/{knowledgeBase-RhFPGWDc.js → knowledgeBase-BBETc5-S.js} +6 -6
  156. package/dist/src/{knowledgeBase-Bpoe_nLu.cjs → knowledgeBase-C8qOo26M.cjs} +5 -5
  157. package/dist/src/{knowledgeBase-lm9RXSAm.js → knowledgeBase-CzAi2rUI.js} +6 -6
  158. package/dist/src/{knowledgeBase-Dgc7CBWF.js → knowledgeBase-Dr3Kib7F.js} +5 -5
  159. package/dist/src/{litellm-C2kqjxqp.js → litellm-BLSiANhk.js} +5 -5
  160. package/dist/src/{litellm-CoyI4IAl.cjs → litellm-CaUmV7Mk.cjs} +4 -4
  161. package/dist/src/{litellm-p37R1dzQ.js → litellm-DQGo_juI.js} +4 -4
  162. package/dist/src/{litellm-DRjpcSa7.js → litellm-DRc4qWfc.js} +5 -5
  163. package/dist/src/{logger-DksKw1Qc.js → logger-BbY6ypFL.js} +2 -2
  164. package/dist/src/{logger-B88EkIn6.js → logger-KD8JjCRJ.js} +2 -2
  165. package/dist/src/{luma-ray-KgTCXrZC.js → luma-ray-B-tNZzqW.js} +6 -6
  166. package/dist/src/{luma-ray-B863CmuZ.js → luma-ray-CtS3OlGq.js} +5 -5
  167. package/dist/src/{luma-ray-BTTLtqQ8.js → luma-ray-PJJgUjOc.js} +6 -6
  168. package/dist/src/{luma-ray-BxVKaW2a.cjs → luma-ray-if-Ml4R9.cjs} +5 -5
  169. package/dist/src/main.js +242 -198
  170. package/dist/src/{messages-zWbkLLHz.js → messages-B9dSjrNf.js} +264 -16
  171. package/dist/src/{messages-811uVVW5.cjs → messages-BnsVHUnm.cjs} +266 -15
  172. package/dist/src/{messages-MYTQ2TWp.js → messages-CI69Lasb.js} +264 -16
  173. package/dist/src/{messages-BTQz42fn.js → messages-CewuNcNS.js} +264 -16
  174. package/dist/src/{meteor-Co1VQ1u5.cjs → meteor-BBGcGeCa.cjs} +1 -1
  175. package/dist/src/{meteor-DuAFv6gF.js → meteor-BKTM-7KS.js} +1 -1
  176. package/dist/src/{meteor-DHdzY1Ss.js → meteor-CeGo0Lu2.js} +2 -2
  177. package/dist/src/{meteor-CU5UAE-H.js → meteor-Wc_aUVvu.js} +2 -2
  178. package/dist/src/{modelslab-wu9yi5GE.js → modelslab-BCLOtfek.js} +7 -7
  179. package/dist/src/{modelslab-Dk1JAtVo.cjs → modelslab-BkapYJhh.cjs} +6 -6
  180. package/dist/src/{modelslab-DIq-6y7x.js → modelslab-D73OnKSx.js} +6 -6
  181. package/dist/src/{modelslab-D0erNWKe.js → modelslab-zpz9JcK0.js} +7 -7
  182. package/dist/src/{nova-reel-CCFRfeRb.js → nova-reel-B8F_TK5w.js} +6 -6
  183. package/dist/src/{nova-reel-DQrm74ng.js → nova-reel-Bx0NFV2f.js} +5 -5
  184. package/dist/src/{nova-reel-gr11WG7f.js → nova-reel-CNGJTLtG.js} +6 -6
  185. package/dist/src/{nova-reel-CrLXVKQf.cjs → nova-reel-DkT7tnoB.cjs} +5 -5
  186. package/dist/src/{nova-sonic-BYdp-QLs.js → nova-sonic-BaXRN1cr.js} +4 -4
  187. package/dist/src/{nova-sonic-TDgrlTk7.js → nova-sonic-BeTRaFOh.js} +4 -4
  188. package/dist/src/{nova-sonic-B_ZXcUJB.js → nova-sonic-CL7Zqv0G.js} +3 -3
  189. package/dist/src/{nova-sonic-i5tUvXKn.cjs → nova-sonic-YT426juD.cjs} +3 -3
  190. package/dist/src/{openai-DhVEmgeZ.js → openai-BMHD2Huo.js} +2 -2
  191. package/dist/src/{openai-Qsvz25mV.js → openai-BT-JvDse.js} +2 -2
  192. package/dist/src/{openai-URNyItar.cjs → openai-Cy1XLs0c.cjs} +1 -1
  193. package/dist/src/{openai-iYtrXzOX.js → openai-D4fxGvRx.js} +1 -1
  194. package/dist/src/{openclaw-CwzlQSQX.js → openclaw-Bq7RVR3k.js} +7 -6
  195. package/dist/src/{openclaw-CLWrW03k.js → openclaw-DA8U4DsD.js} +8 -7
  196. package/dist/src/{openclaw-CnQ363Wi.js → openclaw-DObVgpjC.js} +8 -7
  197. package/dist/src/{openclaw-wX9rtfke.cjs → openclaw-DUBZP3GL.cjs} +8 -7
  198. package/dist/src/{opencode-sdk-BUu5Nevv.js → opencode-sdk-BB40Wir1.js} +4 -4
  199. package/dist/src/{opencode-sdk-GI2KaAXq.js → opencode-sdk-BM1UAIv1.js} +3 -3
  200. package/dist/src/{opencode-sdk-BZ2idgYA.cjs → opencode-sdk-CeqiOcOU.cjs} +4 -4
  201. package/dist/src/{opencode-sdk-BxD8vXp_.js → opencode-sdk-ChdK7F7z.js} +4 -4
  202. package/dist/src/{otlpReceiver-DmVulbhC.js → otlpReceiver-C6thJRXi.js} +4 -4
  203. package/dist/src/{otlpReceiver-B2z58l4e.js → otlpReceiver-CcdIikOu.js} +3 -3
  204. package/dist/src/{otlpReceiver-BfcVq2Nq.cjs → otlpReceiver-DNSQj6bf.cjs} +3 -3
  205. package/dist/src/{otlpReceiver-BntK801g.js → otlpReceiver-UYMQx3sy.js} +4 -4
  206. package/dist/src/{providerRegistry-CPQ_CmVO.js → providerRegistry-1gB5vtzQ.js} +2 -2
  207. package/dist/src/{providerRegistry-CQMdTmHP.cjs → providerRegistry-BESeALrr.cjs} +1 -1
  208. package/dist/src/{providerRegistry-Bvh8mv85.js → providerRegistry-DoACwqhD.js} +1 -1
  209. package/dist/src/{providerRegistry-CWoPjKFZ.js → providerRegistry-PMsleEzs.js} +2 -2
  210. package/dist/src/{providers-Bp4S-FvO.js → providers-BuyzKt7C.js} +1 -1
  211. package/dist/src/{providers-DV3ax9e_.cjs → providers-C7lNVBjX.cjs} +1 -1
  212. package/dist/src/{providers-u9Enmfok.js → providers-CCE2COJi2.js} +1 -1
  213. package/dist/src/{providers-DruaQfwu.js → providers-CJh7iriU.js} +18103 -17952
  214. package/dist/src/{providers-iUt5fbAN.js → providers-Ctcc592x.js} +1 -1
  215. package/dist/src/{providers-Domz_llv.js → providers-DRrerKra.js} +432 -281
  216. package/dist/src/{providers-BV_KMZje.js → providers-DT-GtF2t.js} +19094 -18943
  217. package/dist/src/{providers-1eKkXBKp.cjs → providers-eDShy16E.cjs} +17946 -17795
  218. package/dist/src/{pythonUtils-Cldx7huE.js → pythonUtils-C4tltmIn.js} +3 -3
  219. package/dist/src/{pythonUtils-tAJvvpS-.cjs → pythonUtils-CoLaCwNY.cjs} +3 -3
  220. package/dist/src/{pythonUtils-C2UQ30Rz.js → pythonUtils-DMO68Jg7.js} +3 -3
  221. package/dist/src/{pythonUtils-CnndUbW-.js → pythonUtils-DNqbnRdx.js} +3 -3
  222. package/dist/src/{quiverai-DR0SnIQV.js → quiverai-BSS9a7wV.js} +3 -3
  223. package/dist/src/{quiverai-CtWi6x_g.js → quiverai-Bk1KrvL6.js} +4 -4
  224. package/dist/src/{quiverai-DFotyafY.cjs → quiverai-Bpx6MZ7T.cjs} +3 -3
  225. package/dist/src/{quiverai-aPPvXOgn.js → quiverai-CPKhWgaT.js} +4 -4
  226. package/dist/src/{render-DHIZ6_k8.js → render-7uNJ2V14.js} +2 -2
  227. package/dist/src/{render-CH-62LbA.js → render-DlscvAUJ.js} +1 -1
  228. package/dist/src/{render-CMEpfLaO.js → render-eui5p5mL.js} +2 -2
  229. package/dist/src/{render-CgVDrJmM.js → render-nj-UaPdn.js} +2 -2
  230. package/dist/src/{render-DfQSFxGE.cjs → render-tG6ir9_g.cjs} +1 -1
  231. package/dist/src/{responses--OsX2aYW.js → responses-1ztiVYsx.js} +49 -15
  232. package/dist/src/{responses-DL9m8CyY.js → responses-B8haB-mD.js} +49 -15
  233. package/dist/src/{responses-C-flexAY.js → responses-BiaBguAu.js} +49 -15
  234. package/dist/src/{responses-Bi9vBuW_.cjs → responses-CF-ayauu.cjs} +48 -14
  235. package/dist/src/rubyUtils-4hjGxvju.js +3 -0
  236. package/dist/src/{rubyUtils-DVLeA2jg.js → rubyUtils-BI0p46eZ.js} +3 -3
  237. package/dist/src/{rubyUtils-DsGrTx8R.js → rubyUtils-CIQFnVz4.js} +3 -3
  238. package/dist/src/rubyUtils-CO-tuszQ.cjs +2 -0
  239. package/dist/src/{rubyUtils-CYSQEG4a.js → rubyUtils-DGnoCYL2.js} +3 -3
  240. package/dist/src/{rubyUtils-B6eljPuh.cjs → rubyUtils-DoifqkiA.cjs} +4 -3
  241. package/dist/src/{sagemaker-BveBvuxm.js → sagemaker-BDLeW29y.js} +12 -12
  242. package/dist/src/{sagemaker-D67yzMzs.js → sagemaker-C5T60MKf.js} +13 -13
  243. package/dist/src/{sagemaker-BVkaG2-l.js → sagemaker-ClS_NB07.js} +13 -13
  244. package/dist/src/{sagemaker-XnfhheQv.cjs → sagemaker-ljtY12VM.cjs} +12 -12
  245. package/dist/src/{scanner-1DqWi1Ej.js → scanner-nOCWNIXa.js} +7 -7
  246. package/dist/src/server/index.js +1067 -265
  247. package/dist/src/{server-Dx2TyCH2.cjs → server-BEECpeGG.cjs} +5 -5
  248. package/dist/src/{server-BNYztJkh.js → server-ByiF3qlg.js} +9 -8
  249. package/dist/src/{server-BSB45Nt9.js → server-ByxbqAcQ.js} +8 -7
  250. package/dist/src/{server-DaA2eR26.cjs → server-C0XKRNB_.cjs} +1 -1
  251. package/dist/src/server-C_15p79-.js +3 -0
  252. package/dist/src/{server-D6Il2Sob.js → server-gyd6d4Hc.js} +5 -5
  253. package/dist/src/{signal-CE5G3a7x.js → signal-DTtUuU3l.js} +3 -3
  254. package/dist/src/{slack-acRb0IqQ.js → slack-4zZX1OKP.js} +1 -1
  255. package/dist/src/{slack-1Rhq0EoV.cjs → slack-BLlsDpfG.cjs} +1 -1
  256. package/dist/src/{slack-D5Wpy8LM.js → slack-BPYLQLgb.js} +2 -2
  257. package/dist/src/{slack-DDUe-5MC.js → slack-Bamy_7te.js} +2 -2
  258. package/dist/src/{store-DAAyxcy6.cjs → store-2K0kDi80.cjs} +2 -2
  259. package/dist/src/{store-Dn9HUkdW.js → store-2OXm_eBY.js} +3 -3
  260. package/dist/src/store-BELqNwvz.js +3 -0
  261. package/dist/src/{store-M0b1WfYb.js → store-BPkzEyFM.js} +2 -2
  262. package/dist/src/{store-CYEy5J2D.js → store-CPh25336.js} +3 -3
  263. package/dist/src/store-uQZ4AjPe.cjs +2 -0
  264. package/dist/src/{tables-CsWou1Bx.js → tables-BMSOS2Gg.js} +3 -3
  265. package/dist/src/{tables-DUfh1F7Z.cjs → tables-CXbaZ9y1.cjs} +2 -2
  266. package/dist/src/{tables-C4CH3zRr.js → tables-NlvH23ky.js} +3 -3
  267. package/dist/src/{tables-DQ4WU5tX.js → tables-WgdUZ8Ck.js} +2 -2
  268. package/dist/src/{telemetry-dbaJ0E98.js → telemetry--iqaGyaS.js} +5 -4
  269. package/dist/src/{telemetry-Dsw_faFj.cjs → telemetry-CEQxGnMZ.cjs} +7 -6
  270. package/dist/src/{telemetry-Dvqxv3YC.js → telemetry-CgdVGV8N.js} +4 -3
  271. package/dist/src/{telemetry-CQPez_Jp.js → telemetry-DWdGHvEf.js} +5 -4
  272. package/dist/src/telemetry-DjNoC_n3.cjs +2 -0
  273. package/dist/src/telemetry-ZdPZc0fm.js +3 -0
  274. package/dist/src/{text-BVi-cLPJ.cjs → text-BiNME7QG.cjs} +1 -1
  275. package/dist/src/{text-KvuD2Iko.js → text-D4lz-Jg_.js} +1 -1
  276. package/dist/src/{text-DHxdyQqT.js → text-DDQP0tuQ.js} +1 -1
  277. package/dist/src/{text-CZr46tp_.js → text-NWvfMfkF.js} +1 -1
  278. package/dist/src/{tokenUsageUtils-CXrvO-wA.js → tokenUsageUtils-2wIvAhB3.js} +1 -1
  279. package/dist/src/{tokenUsageUtils-C-bmyHoE.js → tokenUsageUtils-4c780gFd.js} +1 -1
  280. package/dist/src/tokenUsageUtils-BjVkdk18.js +142 -0
  281. package/dist/src/{tokenUsageUtils-Bb7DkZPz.cjs → tokenUsageUtils-C9odhsbW.cjs} +1 -1
  282. package/dist/src/{transcription-DuWDupG7.js → transcription-84t4ALo2.js} +5 -5
  283. package/dist/src/{transcription-CJspiD2c.js → transcription-Bm2emLmJ.js} +6 -6
  284. package/dist/src/{transcription-BvjmiYB1.cjs → transcription-CZ4LG5hQ.cjs} +5 -5
  285. package/dist/src/{transcription-V2HaAmy2.js → transcription-D7Q0vJsh.js} +6 -6
  286. package/dist/src/{transform-zDhMmzwX.js → transform-B-b6Cq-q.js} +5 -5
  287. package/dist/src/transform-BQt0BeAW.js +3 -0
  288. package/dist/src/{transform-DgKlRr73.cjs → transform-Bq5oqC0s.cjs} +1 -1
  289. package/dist/src/{transform-CUnzlsbn.cjs → transform-C9izGX54.cjs} +4 -4
  290. package/dist/src/{transform-DYX1_Xnh.js → transform-CwbAZ84V.js} +5 -5
  291. package/dist/src/{transform-CTeuTR3S.cjs → transform-Dg4LcO1Y.cjs} +6 -6
  292. package/dist/src/{transform-CG0ehZNG.js → transform-DtooZqYY.js} +6 -6
  293. package/dist/src/{transform-UN5UGu8U.js → transform-DzCF-wqV.js} +5 -5
  294. package/dist/src/{transform-lQrDE1BQ.js → transform-_DpNB4qp.js} +5 -5
  295. package/dist/src/{transform-Bbg6A8Jk.js → transform-eGiUAv86.js} +4 -4
  296. package/dist/src/{transformersAvailability-Cju9mHgR.cjs → transformersAvailability-B22swDxr.cjs} +1 -1
  297. package/dist/src/{transformersAvailability-CcHusyhw.js → transformersAvailability-lvCCvuPT.js} +1 -1
  298. package/dist/src/{transformersAvailability-DLlROWhg.js → transformersAvailability-rJGPccjr.js} +1 -1
  299. package/dist/src/{types-Bgh5SOn6.js → types-BDjGOq4E.js} +4 -2
  300. package/dist/src/{types-Dm9JM6Vb.js → types-BVH9hjgW.js} +4 -2
  301. package/dist/src/{types-CeaeaZdP.cjs → types-CgG2rKiW.cjs} +151 -149
  302. package/dist/src/{types-BGQDAP8i.js → types-DNRZVOue.js} +152 -150
  303. package/dist/src/{util-C8e5uydV.js → util-3pBZZb_H.js} +142 -17
  304. package/dist/src/{util-CN3SrLT4.cjs → util-A5_ZsQUn.cjs} +65 -43
  305. package/dist/src/{util-D3q0WQ-0.js → util-B9CNhyac.js} +66 -44
  306. package/dist/src/{util-DxWpWjhc.js → util-BQOCAHQC.js} +700 -575
  307. package/dist/src/{util-BYvQUPp7.js → util-BVXcTwXu.js} +3 -3
  308. package/dist/src/{util-D9TisOyk.js → util-BlFVL0UF.js} +65 -43
  309. package/dist/src/{util-C9J8ahRn.js → util-C-kmRosx.js} +66 -44
  310. package/dist/src/{util-DvU2Pw8c.js → util-DFPeFkiV.js} +3 -3
  311. package/dist/src/{util-DDs-7g6-.js → util-DN0-b81k.js} +3 -3
  312. package/dist/src/{util-olYL5C6N.cjs → util-Dpmm_dAI.cjs} +3 -3
  313. package/dist/src/{util-oGMLA7vc.js → util-Dub0f_ej.js} +700 -575
  314. package/dist/src/{util-Bxn8emtE.cjs → util-DvpHnLt0.cjs} +718 -570
  315. package/dist/src/{utils-DJfvjyMj.js → utils-BUMN8orw.js} +3 -3
  316. package/dist/src/{utils-B05gLxER.cjs → utils-DkVeShIB.cjs} +2 -2
  317. package/dist/src/{utils-BLJKfv0y.js → utils-kt7lv30R.js} +3 -3
  318. package/dist/src/{utils-hXtCYanr.js → utils-o8S5huU2.js} +2 -2
  319. package/dist/src/version-0frU0UTr.js +16 -0
  320. package/dist/src/version-CbpiUINz.js +17 -0
  321. package/dist/src/version-CbuBKu2U.js +16 -0
  322. package/dist/src/version-D9zu9FWB.cjs +27 -0
  323. package/dist/tsconfig.tsbuildinfo +1 -1
  324. package/package.json +22 -20
  325. package/dist/src/app/assets/Report-CQYFezYu.js +0 -1
  326. package/dist/src/app/assets/index-BzJt18Jz.js +0 -385
  327. package/dist/src/cache-Cr9oLMUa.js +0 -3
  328. package/dist/src/cloud-Hphvo8kr.js +0 -3
  329. package/dist/src/codex-sdk-BAmYE7qy.js +0 -3
  330. package/dist/src/evalResult-D8MT9p0s.js +0 -3
  331. package/dist/src/evalResult-Dvc-iucu.cjs +0 -2
  332. package/dist/src/evaluator-CVessDWe.js +0 -3
  333. package/dist/src/fetch-C7bGKDlQ.js +0 -3
  334. package/dist/src/graders-BOAzQEUe.cjs +0 -2
  335. package/dist/src/graders-D4BTsZdG2.js +0 -3
  336. package/dist/src/graders-DOJK1XpV.js +0 -2
  337. package/dist/src/graders-NAv9LcBn.js +0 -2
  338. package/dist/src/rubyUtils-D1L2d3jb.js +0 -3
  339. package/dist/src/rubyUtils-DUbq4tff.cjs +0 -2
  340. package/dist/src/server-DCtHUqlp.js +0 -3
  341. package/dist/src/store-CWOSz6D_.cjs +0 -2
  342. package/dist/src/store-DCDBhv7B.js +0 -3
  343. package/dist/src/telemetry-C1IqxcdW.js +0 -3
  344. package/dist/src/telemetry-C4ZEa_es.cjs +0 -2
  345. package/dist/src/transform-M6ITAESf.js +0 -3
  346. /package/dist/src/{evalResult-DElBuddX.js → evalResult-spPqh1G_.js} +0 -0
@@ -1,14 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import { N as state, T as getEnvBool, m as sanitizeObject, s as logger } from "./logger-DksKw1Qc.js";
3
- import { F as VERSION, k as TERMINAL_MAX_WIDTH, n as fetchWithProxy } from "./fetch-BEWnXrrG.js";
4
- import { t as invariant } from "./invariant-BtWWVVhl.js";
5
- import { m as isProviderOptions, o as OutputFileExtension, p as isApiProvider, s as ResultFailureReason } from "./types-Dm9JM6Vb.js";
6
- import { o as safeResolve, r as importModule, t as getDirectory } from "./esm-tVgYPY-f.js";
7
- import { a as getNunjucksEngine, n as renderVarsInObject } from "./render-CMEpfLaO.js";
8
- import { i as isJavascriptFile, t as JAVASCRIPT_EXTENSIONS } from "./fileExtensions-AWa2ZML4.js";
9
- import { r as runPython } from "./pythonUtils-CnndUbW-.js";
2
+ import { N as state, T as getEnvBool, m as sanitizeObject, s as logger } from "./logger-BbY6ypFL.js";
3
+ import { k as TERMINAL_MAX_WIDTH, n as fetchWithProxy } from "./fetch-B6ch2nU2.js";
4
+ import { n as VERSION } from "./version-CbpiUINz.js";
5
+ import { t as invariant } from "./invariant-B2Rf6avk.js";
6
+ import { m as isProviderOptions, o as OutputFileExtension, p as isApiProvider, s as ResultFailureReason } from "./types-BVH9hjgW.js";
7
+ import { o as safeResolve, r as importModule, t as getDirectory } from "./esm-BX8fwlAO.js";
8
+ import { a as getNunjucksEngine, n as renderVarsInObject } from "./render-eui5p5mL.js";
9
+ import { i as isJavascriptFile, t as JAVASCRIPT_EXTENSIONS } from "./fileExtensions-DysCsxNG.js";
10
+ import { r as runPython } from "./pythonUtils-DNqbnRdx.js";
10
11
  import dotenv from "dotenv";
11
12
  import * as fs$2 from "fs";
13
+ import fs from "fs";
12
14
  import * as path$1 from "path";
13
15
  import path from "path";
14
16
  import * as os$1 from "os";
@@ -21,670 +23,793 @@ import { parse as parse$1 } from "csv-parse/sync";
21
23
  import { globSync, hasMagic } from "glob";
22
24
  import { XMLBuilder } from "fast-xml-parser";
23
25
  import { stringify } from "csv-stringify/sync";
24
- //#region src/util/provider.ts
25
- function canonicalizeProviderId(id) {
26
- if (id.startsWith("file://")) {
27
- const filePath = id.slice(7);
28
- return path$1.isAbsolute(filePath) ? id : `file://${path$1.resolve(filePath)}`;
29
- }
30
- for (const prefix of [
31
- "exec:",
32
- "python:",
33
- "golang:"
34
- ]) if (id.startsWith(prefix)) {
35
- const filePath = id.slice(prefix.length);
36
- if (filePath.includes("/") || filePath.includes("\\")) return `${prefix}${path$1.resolve(filePath)}`;
37
- return id;
38
- }
39
- if ((id.endsWith(".js") || id.endsWith(".ts") || id.endsWith(".mjs")) && (id.includes("/") || id.includes("\\"))) return `file://${path$1.resolve(id)}`;
40
- return id;
41
- }
42
- function getProviderLabel(provider) {
43
- return provider?.label && typeof provider.label === "string" ? provider.label : void 0;
44
- }
45
- function providerToIdentifier(provider) {
46
- if (!provider) return;
47
- if (typeof provider === "string") return canonicalizeProviderId(provider);
48
- const label = getProviderLabel(provider);
49
- if (label) return label;
50
- if (isApiProvider(provider)) return canonicalizeProviderId(provider.id());
51
- if (isProviderOptions(provider)) {
52
- if (provider.id) return canonicalizeProviderId(provider.id);
53
- return;
54
- }
55
- if (typeof provider === "object" && "id" in provider && typeof provider.id === "string") return canonicalizeProviderId(provider.id);
56
- }
57
- /**
58
- * Gets a descriptive identifier string for a provider, showing both label and ID when both exist.
59
- * Useful for error messages to help users debug provider reference issues.
60
- */
61
- function getProviderDescription(provider) {
62
- const label = provider.label;
63
- const id = provider.id();
64
- if (label && label !== id) return `${label} (${id})`;
65
- return id;
66
- }
26
+ //#region src/util/functions/loadFunction.ts
27
+ const functionCache = {};
67
28
  /**
68
- * Checks if a provider reference matches a given provider.
69
- * Supports exact matching and wildcard patterns.
29
+ * Loads a function from a JavaScript or Python file
30
+ * @param options Options for loading the function
31
+ * @returns The loaded function
70
32
  */
71
- function doesProviderRefMatch(ref, provider) {
72
- const label = provider.label;
73
- const id = provider.id();
74
- const canonicalRef = canonicalizeProviderId(ref);
75
- const canonicalId = canonicalizeProviderId(id);
76
- if (label && label === ref) return true;
77
- if (id === ref || canonicalId === canonicalRef) return true;
78
- if (ref.endsWith("*")) {
79
- const prefix = ref.slice(0, -1);
80
- if (label?.startsWith(prefix) || id.startsWith(prefix) || canonicalId.startsWith(prefix)) return true;
33
+ async function loadFunction({ filePath, functionName, defaultFunctionName = "func", basePath = state.basePath, useCache = true }) {
34
+ const cacheKey = `${filePath}${functionName ? `:${functionName}` : ""}`;
35
+ if (useCache && functionCache[cacheKey]) return functionCache[cacheKey];
36
+ const resolvedPath = basePath ? path.resolve(basePath, filePath) : filePath;
37
+ if (!isJavascriptFile(resolvedPath) && !resolvedPath.endsWith(".py")) throw new Error(`File must be a JavaScript (${JAVASCRIPT_EXTENSIONS.join(", ")}) or Python (.py) file`);
38
+ try {
39
+ let func;
40
+ if (isJavascriptFile(resolvedPath)) {
41
+ const module = await importModule(resolvedPath, functionName);
42
+ let moduleFunc;
43
+ if (functionName) moduleFunc = module;
44
+ else moduleFunc = typeof module === "function" ? module : module?.default?.default || module?.default || module?.[defaultFunctionName] || module;
45
+ 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}")`);
46
+ func = moduleFunc;
47
+ } else {
48
+ const result = (...args) => runPython(resolvedPath, functionName || defaultFunctionName, args);
49
+ func = result;
50
+ }
51
+ if (useCache) functionCache[cacheKey] = func;
52
+ return func;
53
+ } catch (err) {
54
+ logger.error(`Failed to load function: ${err.message}`);
55
+ throw err;
81
56
  }
82
- if (label?.startsWith(`${ref}:`) || id.startsWith(`${ref}:`) || canonicalId.startsWith(`${ref}:`)) return true;
83
- return false;
84
57
  }
85
58
  /**
86
- * Checks if a provider is allowed based on a list of allowed references.
59
+ * Extracts the file path and function name from a file:// URL
60
+ * @param fileUrl The file:// URL (e.g., "file://path/to/file.js:functionName")
61
+ * @returns The file path and optional function name
87
62
  */
88
- function isProviderAllowed(provider, allowedProviders) {
89
- if (!Array.isArray(allowedProviders)) return true;
90
- if (allowedProviders.length === 0) return false;
91
- return allowedProviders.some((ref) => doesProviderRefMatch(ref, provider));
63
+ function parseFileUrl(fileUrl) {
64
+ if (!fileUrl.startsWith("file://")) throw new Error("URL must start with file://");
65
+ const urlWithoutProtocol = fileUrl.slice(7);
66
+ const lastColonIndex = urlWithoutProtocol.lastIndexOf(":");
67
+ if (lastColonIndex > 1) return {
68
+ filePath: urlWithoutProtocol.slice(0, lastColonIndex),
69
+ functionName: urlWithoutProtocol.slice(lastColonIndex + 1)
70
+ };
71
+ return { filePath: urlWithoutProtocol };
92
72
  }
73
+ //#endregion
74
+ //#region src/util/file.ts
93
75
  /**
94
- * Detects if a provider uses OpenAI models.
95
- * This includes direct OpenAI providers and Azure OpenAI.
76
+ * Simple Nunjucks engine specifically for file paths
77
+ * This function is separate from the main getNunjucksEngine to avoid circular dependencies
96
78
  */
97
- function isOpenAiProvider(providerId) {
98
- const lowerProviderId = providerId.toLowerCase();
99
- if (lowerProviderId.startsWith("openai:")) return true;
100
- if (lowerProviderId.startsWith("azureopenai:")) return true;
101
- if (lowerProviderId.startsWith("azure:")) {
102
- if ([
103
- "gpt",
104
- "openai",
105
- "davinci",
106
- "curie",
107
- "babbage",
108
- "ada",
109
- "text-embedding",
110
- "whisper",
111
- "dall-e",
112
- "tts"
113
- ].some((indicator) => lowerProviderId.includes(indicator))) return true;
114
- }
115
- return false;
79
+ function getNunjucksEngineForFilePath() {
80
+ const env = nunjucks.configure({ autoescape: false });
81
+ env.addGlobal("env", {
82
+ ...process.env,
83
+ ...state.config?.env
84
+ });
85
+ return env;
116
86
  }
117
87
  /**
118
- * Detects if a provider uses Anthropic/Claude models.
119
- * This includes direct Anthropic providers, Bedrock with Claude, and Vertex with Claude.
88
+ * Loads content from an external file if the input is a file path, otherwise
89
+ * returns the input as-is. Supports Nunjucks templating for file paths.
90
+ *
91
+ * @param filePath - The input to process. Can be a file path string starting with "file://",
92
+ * an array of file paths, or any other type of data.
93
+ * @param context - Optional context to control file loading behavior. 'assertion' context
94
+ * preserves Python/JS file references instead of loading their content.
95
+ * @returns The loaded content if the input was a file path, otherwise the original input.
96
+ * For JSON and YAML files, the content is parsed into an object.
97
+ * For other file types, the raw file content is returned as a string.
98
+ *
99
+ * @throws {Error} If the specified file does not exist.
120
100
  */
121
- function isAnthropicProvider(providerId) {
122
- const lowerProviderId = providerId.toLowerCase();
123
- if (lowerProviderId.startsWith("anthropic:")) return true;
124
- if (lowerProviderId.startsWith("bedrock:")) {
125
- if (lowerProviderId.includes("claude") || lowerProviderId.includes("anthropic")) return true;
101
+ function maybeLoadFromExternalFile(filePath, context) {
102
+ if (Array.isArray(filePath)) return filePath.map((path) => {
103
+ return maybeLoadFromExternalFile(path, context);
104
+ });
105
+ if (typeof filePath !== "string") return filePath;
106
+ if (!filePath.startsWith("file://")) return filePath;
107
+ const renderedFilePath = getNunjucksEngineForFilePath().renderString(filePath, {});
108
+ const { filePath: cleanPath, functionName } = parseFileUrl(renderedFilePath);
109
+ if (context === "assertion" && (cleanPath.endsWith(".py") || isJavascriptFile(cleanPath))) {
110
+ logger.debug(`Preserving Python/JS file reference in assertion context: ${renderedFilePath}`);
111
+ return renderedFilePath;
126
112
  }
127
- if (lowerProviderId.startsWith("vertex:")) {
128
- if (lowerProviderId.includes("claude")) return true;
113
+ if (context === "vars") {
114
+ logger.debug(`Preserving file reference in vars context: ${renderedFilePath}`);
115
+ return renderedFilePath;
129
116
  }
130
- return false;
131
- }
132
- const KNOWN_ENV_VARS = {
133
- openai: "OPENAI_API_KEY",
134
- anthropic: "ANTHROPIC_API_KEY",
135
- google: "GOOGLE_API_KEY",
136
- mistral: "MISTRAL_API_KEY",
137
- cohere: "COHERE_API_KEY",
138
- replicate: "REPLICATE_API_TOKEN",
139
- voyage: "VOYAGE_API_KEY",
140
- ai21: "AI21_API_KEY",
141
- xai: "XAI_API_KEY",
142
- groq: "GROQ_API_KEY",
143
- deepseek: "DEEPSEEK_API_KEY",
144
- perplexity: "PERPLEXITY_API_KEY",
145
- hyperbolic: "HYPERBOLIC_API_KEY",
146
- cerebras: "CEREBRAS_API_KEY",
147
- togetherai: "TOGETHER_API_KEY",
148
- fal: "FAL_KEY",
149
- huggingface: "HF_TOKEN",
150
- "cloudflare-ai": "CLOUDFLARE_API_KEY"
151
- };
152
- function getDefaultEnvVar(providerId) {
153
- const prefix = providerId.split(":")[0];
154
- return KNOWN_ENV_VARS[prefix] || `${prefix.toUpperCase()}_API_KEY`;
155
- }
156
- /**
157
- * Pre-checks providers for missing API keys before evaluation starts.
158
- * Assumes getApiKey() is side-effect free (no network calls or token refresh).
159
- */
160
- function checkProviderApiKeys(providers) {
161
- const missingApiKeys = /* @__PURE__ */ new Map();
162
- for (const provider of providers) {
163
- const p = provider;
164
- if (typeof p.getApiKey !== "function") continue;
165
- if (provider.id().startsWith("azure:")) continue;
166
- const requiresKey = typeof p.requiresApiKey === "function" ? p.requiresApiKey() : p.config?.apiKeyRequired !== false;
167
- let apiKey;
168
- try {
169
- apiKey = p.getApiKey();
170
- } catch {
171
- apiKey = void 0;
172
- }
173
- if (requiresKey && !apiKey) {
174
- const envVar = p.config?.apiKeyEnvar || getDefaultEnvVar(provider.id());
175
- if (!missingApiKeys.has(envVar)) missingApiKeys.set(envVar, []);
176
- missingApiKeys.get(envVar).push(provider.id());
117
+ if (functionName && (cleanPath.endsWith(".py") || isJavascriptFile(cleanPath))) return renderedFilePath;
118
+ const pathToUse = functionName && !(cleanPath.endsWith(".py") || isJavascriptFile(cleanPath)) ? renderedFilePath.slice(7) : cleanPath;
119
+ const resolvedPath = path$1.resolve(state.basePath || "", pathToUse);
120
+ if (hasMagic(pathToUse)) {
121
+ const matchedFiles = globSync(resolvedPath, { windowsPathsNoEscape: true });
122
+ if (matchedFiles.length === 0) throw new Error(`No files found matching pattern: ${resolvedPath}`);
123
+ const allContents = [];
124
+ for (const matchedFile of matchedFiles) {
125
+ let contents;
126
+ try {
127
+ contents = fs$2.readFileSync(matchedFile, "utf8");
128
+ } catch (error) {
129
+ if (error.code === "ENOENT") {
130
+ logger.debug(`File disappeared during glob expansion: ${matchedFile}`);
131
+ continue;
132
+ }
133
+ throw error;
134
+ }
135
+ if (matchedFile.endsWith(".json")) {
136
+ const parsed = JSON.parse(contents);
137
+ if (Array.isArray(parsed)) allContents.push(...parsed);
138
+ else allContents.push(parsed);
139
+ } else if (matchedFile.endsWith(".yaml") || matchedFile.endsWith(".yml")) {
140
+ const parsed = yaml.load(contents);
141
+ if (parsed === null || parsed === void 0) continue;
142
+ if (Array.isArray(parsed)) allContents.push(...parsed);
143
+ else allContents.push(parsed);
144
+ } else if (matchedFile.endsWith(".csv")) {
145
+ const records = parse$1(contents, { columns: true });
146
+ if (records.length > 0 && Object.keys(records[0]).length === 1) allContents.push(...records.map((record) => Object.values(record)[0]));
147
+ else allContents.push(...records);
148
+ } else allContents.push(contents);
177
149
  }
150
+ return allContents;
178
151
  }
179
- return missingApiKeys;
180
- }
181
- /**
182
- * Detects if a provider uses Google models.
183
- * This includes direct Google/Vertex providers with Gemini and other Google models.
184
- * Note: Vertex with Claude models is NOT counted as Google (it's Anthropic).
185
- */
186
- function isGoogleProvider(providerId) {
187
- const lowerProviderId = providerId.toLowerCase();
188
- if (lowerProviderId.startsWith("google:")) return true;
189
- if (lowerProviderId.startsWith("vertex:")) {
190
- if (!lowerProviderId.includes("claude")) return true;
152
+ const finalPath = resolvedPath;
153
+ let contents;
154
+ try {
155
+ contents = fs$2.readFileSync(finalPath, "utf8");
156
+ } catch (error) {
157
+ if (error.code === "ENOENT") throw new Error(`File does not exist: ${finalPath}`);
158
+ throw new Error(`Failed to read file ${finalPath}: ${error}`);
191
159
  }
192
- return false;
160
+ if (finalPath.endsWith(".json")) try {
161
+ return JSON.parse(contents);
162
+ } catch (error) {
163
+ throw new Error(`Failed to parse JSON file ${finalPath}: ${error}`);
164
+ }
165
+ if (finalPath.endsWith(".yaml") || finalPath.endsWith(".yml")) try {
166
+ return yaml.load(contents);
167
+ } catch (error) {
168
+ throw new Error(`Failed to parse YAML file ${finalPath}: ${error}`);
169
+ }
170
+ if (finalPath.endsWith(".csv")) {
171
+ const records = parse$1(contents, { columns: true });
172
+ if (records.length > 0 && Object.keys(records[0]).length === 1) return records.map((record) => Object.values(record)[0]);
173
+ return records;
174
+ }
175
+ return contents;
193
176
  }
194
- //#endregion
195
- //#region src/util/comparison.ts
196
177
  /**
197
- * Explicit runtime variable names that don't follow the underscore convention.
198
- * These are added during evaluation but aren't part of the original test definition.
199
- *
200
- * - sessionId: Added by multi-turn strategy providers (GOAT, Crescendo)
201
- *
202
- * Note: Variables starting with underscore (e.g., _conversation) are automatically
203
- * treated as runtime variables and filtered out.
204
- */
205
- const EXPLICIT_RUNTIME_VAR_KEYS = ["sessionId"];
206
- /**
207
- * Checks if a variable key is a runtime-only variable that should be filtered
208
- * when comparing test cases.
178
+ * Resolves a relative file path with respect to a base path, handling cloud configuration appropriately.
179
+ * When using a cloud configuration, the current working directory is always used instead of the context's base path.
209
180
  *
210
- * Runtime variables are identified by:
211
- * 1. Starting with underscore (_) - convention for internal/runtime vars
212
- * 2. Being in the explicit runtime var list (for legacy vars like sessionId)
181
+ * @param filePath - The relative or absolute file path to resolve.
182
+ * @param isCloudConfig - Whether this is a cloud configuration.
183
+ * @returns The resolved absolute file path.
213
184
  */
214
- function isRuntimeVar(key) {
215
- return key.startsWith("_") || EXPLICIT_RUNTIME_VAR_KEYS.includes(key);
185
+ function getResolvedRelativePath(filePath, isCloudConfig) {
186
+ if (path$1.isAbsolute(filePath) || !isCloudConfig) return filePath;
187
+ return path$1.join(process.cwd(), filePath);
216
188
  }
217
189
  /**
218
- * Filters out runtime-only variables that are added during evaluation
219
- * but aren't part of the original test definition.
220
- *
221
- * This is used when comparing test cases to determine if a result
222
- * corresponds to a particular test, regardless of runtime state.
190
+ * Recursively loads external file references from a configuration object.
223
191
  *
224
- * Runtime variables are identified by:
225
- * - Starting with underscore (e.g., _conversation, _metadata)
226
- * - Being in the explicit list (e.g., sessionId for backward compatibility)
192
+ * @param config - The configuration object to process
193
+ * @param context - Optional context to control file loading behavior
194
+ * @returns The configuration with external file references resolved
227
195
  */
228
- function filterRuntimeVars(vars) {
229
- if (!vars || typeof vars !== "object" || Array.isArray(vars)) return vars;
230
- const filtered = {};
231
- for (const [key, value] of Object.entries(vars)) if (!isRuntimeVar(key)) filtered[key] = value;
232
- return filtered;
196
+ function maybeLoadConfigFromExternalFile(config, context) {
197
+ if (Array.isArray(config)) return config.map((item) => maybeLoadConfigFromExternalFile(item, context));
198
+ if (typeof config === "object" && config !== null) {
199
+ const result = {};
200
+ for (const key of Object.keys(config)) {
201
+ const childContext = key === "value" && "type" in config && typeof config.type === "string" && (config.type === "python" || config.type === "javascript") ? "assertion" : key === "vars" ? "vars" : context;
202
+ result[key] = maybeLoadConfigFromExternalFile(config[key], childContext);
203
+ }
204
+ return result;
205
+ }
206
+ return maybeLoadFromExternalFile(config, context);
233
207
  }
234
208
  /**
235
- * Extracts only runtime variables from a vars object.
236
- * This is the inverse of filterRuntimeVars.
237
- *
238
- * Used to restore runtime state when re-running filtered tests.
209
+ * Parses a file path or glob pattern to extract function names and file extensions.
210
+ * Function names can be specified in the filename like this:
211
+ * prompt.py:myFunction or prompts.js:myFunction.
212
+ * @param basePath - The base path for file resolution.
213
+ * @param promptPath - The path or glob pattern.
214
+ * @returns Parsed details including function name, file extension, and directory status.
239
215
  */
240
- function extractRuntimeVars(vars) {
241
- if (!vars || typeof vars !== "object" || Array.isArray(vars)) return;
242
- const extracted = {};
243
- for (const [key, value] of Object.entries(vars)) if (isRuntimeVar(key)) extracted[key] = value;
244
- return Object.keys(extracted).length > 0 ? extracted : void 0;
216
+ function parsePathOrGlob(basePath, promptPath) {
217
+ if (promptPath.startsWith("file://")) promptPath = promptPath.slice(7);
218
+ const filePath = path$1.resolve(basePath, promptPath);
219
+ let filename = path$1.relative(basePath, filePath);
220
+ let functionName;
221
+ if (filename.includes(":")) {
222
+ const lastColonIndex = filename.lastIndexOf(":");
223
+ if (lastColonIndex > 1) {
224
+ const pathWithoutFunction = filename.slice(0, lastColonIndex);
225
+ if (isJavascriptFile(pathWithoutFunction) || pathWithoutFunction.endsWith(".py") || pathWithoutFunction.endsWith(".go") || pathWithoutFunction.endsWith(".rb")) {
226
+ functionName = filename.slice(lastColonIndex + 1);
227
+ filename = pathWithoutFunction;
228
+ }
229
+ }
230
+ }
231
+ let stats;
232
+ try {
233
+ stats = fs$2.statSync(path$1.join(basePath, filename));
234
+ } catch (err) {
235
+ if (getEnvBool("PROMPTFOO_STRICT_FILES")) throw err;
236
+ }
237
+ const normalizedFilePath = filePath.replace(/\\/g, "/");
238
+ const isPathPattern = stats?.isDirectory() || hasMagic(promptPath) || hasMagic(normalizedFilePath);
239
+ const safeFilename = path$1.relative(basePath, safeResolve(basePath, filename));
240
+ return {
241
+ extension: isPathPattern ? void 0 : path$1.parse(safeFilename).ext,
242
+ filePath: path$1.join(basePath, safeFilename),
243
+ functionName,
244
+ isPathPattern
245
+ };
246
+ }
247
+ function readOutput(outputPath) {
248
+ const ext = path$1.parse(outputPath).ext.slice(1);
249
+ switch (ext) {
250
+ case "json": return JSON.parse(fs$2.readFileSync(outputPath, "utf-8"));
251
+ default: throw new Error(`Unsupported output file format: ${ext} currently only supports json`);
252
+ }
245
253
  }
246
- function varsMatch(vars1, vars2) {
247
- return deepEqual(vars1, vars2);
254
+ /**
255
+ * Load custom Nunjucks filters from external files.
256
+ * Note: If a glob pattern matches multiple files, only the last file's export is used.
257
+ * Each filter name should typically resolve to a single file.
258
+ */
259
+ async function readFilters(filters, basePath = "") {
260
+ const ret = {};
261
+ for (const [name, filterPath] of Object.entries(filters)) {
262
+ const filePaths = globSync(path$1.join(basePath, filterPath), { windowsPathsNoEscape: true });
263
+ for (const filePath of filePaths) ret[name] = await importModule(path$1.resolve(filePath));
264
+ }
265
+ return ret;
248
266
  }
249
267
  /**
250
- * Generate a unique key for a test case for deduplication purposes.
251
- * Excludes runtime variables and includes strategyId to distinguish tests
252
- * with the same prompt but different strategies.
268
+ * Loads configuration from an external file with variable rendering.
269
+ * This is a convenience wrapper that combines renderVarsInObject and maybeLoadFromExternalFile.
253
270
  *
254
- * @param testCase - The test case to generate a key for
255
- * @returns A JSON string that uniquely identifies the test case
271
+ * Use this for simple config fields that:
272
+ * - Need variable rendering ({{ vars.x }}, {{ env.X }})
273
+ * - May reference external files (file://path.json)
274
+ * - Don't have nested file references that need loading
275
+ *
276
+ * For fields with nested file references (like response_format.schema),
277
+ * use maybeLoadResponseFormatFromExternalFile instead.
278
+ *
279
+ * @param config - The configuration to process
280
+ * @param vars - Variables for template rendering
281
+ * @returns The processed configuration with variables rendered and files loaded
256
282
  */
257
- function getTestCaseDeduplicationKey(testCase) {
258
- const filteredVars = filterRuntimeVars(testCase.vars);
259
- const strategyId = testCase.metadata?.strategyId || "none";
260
- return JSON.stringify({
261
- vars: filteredVars,
262
- strategyId
263
- });
283
+ function maybeLoadFromExternalFileWithVars(config, vars) {
284
+ return maybeLoadFromExternalFile(renderVarsInObject(config, vars));
264
285
  }
265
286
  /**
266
- * Deduplicates an array of test cases based on their vars and strategyId.
267
- * Tests with the same vars but different strategies are considered different.
268
- * Runtime variables (like _conversation, sessionId) are filtered out before comparison.
287
+ * Loads response_format configuration from an external file with variable rendering.
269
288
  *
270
- * @param tests - Array of test cases to deduplicate
271
- * @returns Deduplicated array of test cases
289
+ * This function handles the special case where response_format may contain:
290
+ * 1. A top-level file reference (file://format.json)
291
+ * 2. A nested schema reference for json_schema type (schema: file://schema.json)
292
+ *
293
+ * Both levels need variable rendering and file loading.
294
+ *
295
+ * @param responseFormat - The response_format configuration
296
+ * @param vars - Variables for template rendering
297
+ * @returns The processed response_format with all files loaded
272
298
  */
273
- function deduplicateTestCases(tests) {
274
- const seen = /* @__PURE__ */ new Set();
275
- return tests.filter((test) => {
276
- const key = getTestCaseDeduplicationKey(test);
277
- if (seen.has(key)) return false;
278
- seen.add(key);
279
- return true;
280
- });
281
- }
282
- function resultIsForTestCase(result, testCase) {
283
- const testProviderId = testCase.provider ? providerToIdentifier(testCase.provider) : void 0;
284
- const resultProviderId = providerToIdentifier(result.provider);
285
- const providersMatch = !testProviderId || !resultProviderId || testProviderId === resultProviderId;
286
- const resultVars = filterRuntimeVars(result.vars);
287
- const testVars = filterRuntimeVars(testCase.vars);
288
- const doVarsMatch = varsMatch(testVars, resultVars);
289
- const isMatch = doVarsMatch && providersMatch;
290
- if (!isMatch) {
291
- const varKeys = testVars ? Object.keys(testVars).join(", ") : "none";
292
- logger.debug(`[resultIsForTestCase] No match: vars=${doVarsMatch}, providers=${providersMatch}`, {
293
- testProvider: testProviderId || "none",
294
- resultProvider: resultProviderId || "none",
295
- testVarKeys: varKeys
296
- });
299
+ function maybeLoadResponseFormatFromExternalFile(responseFormat, vars) {
300
+ if (responseFormat === void 0 || responseFormat === null) return responseFormat;
301
+ const loaded = maybeLoadFromExternalFile(renderVarsInObject(responseFormat, vars));
302
+ if (!loaded || typeof loaded !== "object") return loaded;
303
+ if (loaded.type === "json_schema") {
304
+ const nestedSchema = loaded.schema || loaded.json_schema?.schema;
305
+ if (nestedSchema) {
306
+ const loadedSchema = maybeLoadFromExternalFile(renderVarsInObject(nestedSchema, vars));
307
+ if (loaded.schema !== void 0) return {
308
+ ...loaded,
309
+ schema: loadedSchema
310
+ };
311
+ else if (loaded.json_schema?.schema !== void 0) return {
312
+ ...loaded,
313
+ json_schema: {
314
+ ...loaded.json_schema,
315
+ schema: loadedSchema
316
+ }
317
+ };
318
+ }
297
319
  }
298
- return isMatch;
320
+ return loaded;
299
321
  }
300
- //#endregion
301
- //#region src/util/env.ts
302
322
  /**
303
- * Load environment variables from .env file(s).
304
- * @param envPath - Single path, array of paths, or undefined for default .env loading.
305
- * When paths are explicitly specified, all files must exist or an error is thrown.
306
- * When multiple files are provided, later files override values from earlier files.
323
+ * Renders variables in a tools object and loads from external file if applicable.
324
+ * This function combines renderVarsInObject and maybeLoadFromExternalFile into a single step
325
+ * specifically for handling tools configurations.
326
+ *
327
+ * Supports loading from JSON, YAML, Python, and JavaScript files.
328
+ *
329
+ * @param tools - The tools configuration object or array to process.
330
+ * @param vars - Variables to use for rendering.
331
+ * @returns The processed tools configuration with variables rendered and content loaded from files if needed.
332
+ * @throws {Error} If the loaded tools are in an invalid format
307
333
  */
308
- function setupEnv(envPath) {
309
- if (envPath) {
310
- const paths = (Array.isArray(envPath) ? envPath : [envPath]).flatMap((p) => p.includes(",") ? p.split(",").map((s) => s.trim()) : p.trim()).filter((p) => p.length > 0);
311
- if (paths.length === 0) {
312
- dotenv.config({ quiet: true });
313
- return;
334
+ async function maybeLoadToolsFromExternalFile(tools, vars) {
335
+ const rendered = renderVarsInObject(tools, vars);
336
+ if (typeof rendered === "string" && rendered.startsWith("file://")) {
337
+ const { filePath, functionName } = parseFileUrl(rendered);
338
+ if (functionName && (filePath.endsWith(".py") || isJavascriptFile(filePath))) {
339
+ const fileType = filePath.endsWith(".py") ? "Python" : "JavaScript";
340
+ logger.debug(`[maybeLoadToolsFromExternalFile] Loading tools from ${fileType} file: ${filePath}:${functionName}`);
341
+ try {
342
+ let toolDefinitions;
343
+ if (filePath.endsWith(".py")) {
344
+ const absPath = safeResolve(state.basePath || process.cwd(), filePath);
345
+ logger.debug(`[maybeLoadToolsFromExternalFile] Resolved Python path: ${absPath}`);
346
+ toolDefinitions = await runPython(absPath, functionName, []);
347
+ } else {
348
+ const absPath = safeResolve(state.basePath || process.cwd(), filePath);
349
+ logger.debug(`[maybeLoadToolsFromExternalFile] Resolved JavaScript path: ${absPath}`);
350
+ const module = await importModule(absPath);
351
+ const fn = module[functionName] || module.default?.[functionName];
352
+ if (typeof fn !== "function") {
353
+ const availableExports = Object.keys(module).filter((k) => k !== "default");
354
+ const basePath = state.basePath || process.cwd();
355
+ throw new Error(`Function "${functionName}" not found in ${filePath}. Available exports: ${availableExports.length > 0 ? availableExports.join(", ") : "(none)"}\nResolved from: ${basePath}`);
356
+ }
357
+ toolDefinitions = await Promise.resolve(fn());
358
+ }
359
+ 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}`);
360
+ logger.debug(`[maybeLoadToolsFromExternalFile] Successfully loaded ${Array.isArray(toolDefinitions) ? toolDefinitions.length : "object"} tools`);
361
+ return toolDefinitions;
362
+ } catch (err) {
363
+ const errorMessage = err instanceof Error ? err.message : String(err);
364
+ const basePath = state.basePath || process.cwd();
365
+ 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}`);
366
+ }
314
367
  }
315
- for (const p of paths) if (!fs$2.existsSync(p)) throw new Error(`Environment file not found: ${p}`);
316
- if (paths.length === 1) logger.info(`Loading environment variables from ${paths[0]}`);
317
- else logger.info(`Loading environment variables from: ${paths.join(", ")}`);
318
- const pathArg = paths.length === 1 ? paths[0] : paths;
319
- dotenv.config({
320
- path: pathArg,
321
- override: true,
322
- quiet: true
323
- });
324
- } else dotenv.config({ quiet: true });
368
+ if (filePath.endsWith(".py") || isJavascriptFile(filePath)) {
369
+ const ext = filePath.endsWith(".py") ? "Python" : "JavaScript";
370
+ const basePath = state.basePath || process.cwd();
371
+ 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}`);
372
+ }
373
+ }
374
+ if (Array.isArray(rendered)) {
375
+ const results = await Promise.all(rendered.map((item) => maybeLoadToolsFromExternalFile(item, vars)));
376
+ if (results.every((r) => Array.isArray(r))) return results.flat();
377
+ return results;
378
+ }
379
+ if (typeof rendered !== "string") return rendered;
380
+ const loaded = maybeLoadFromExternalFile(rendered);
381
+ if (loaded !== void 0 && loaded !== null && typeof loaded === "string") {
382
+ 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.`);
383
+ 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");
384
+ 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.");
385
+ }
386
+ return loaded;
325
387
  }
326
388
  //#endregion
327
- //#region src/util/functions/loadFunction.ts
328
- const functionCache = {};
389
+ //#region src/util/providerRef.ts
390
+ const PROVIDER_OPTION_KEYS = new Set([
391
+ "id",
392
+ "label",
393
+ "config",
394
+ "prompts",
395
+ "transform",
396
+ "delay",
397
+ "env",
398
+ "inputs"
399
+ ]);
400
+ /** Returns true if the value is a non-empty string suitable as a provider identifier. */
401
+ function isValidProviderId(id) {
402
+ return typeof id === "string" && id !== "";
403
+ }
404
+ function getProviderLabel(provider) {
405
+ if ((typeof provider === "object" || typeof provider === "function") && provider !== null && "label" in provider && typeof provider.label === "string") return provider.label;
406
+ }
329
407
  /**
330
- * Loads a function from a JavaScript or Python file
331
- * @param options Options for loading the function
332
- * @returns The loaded function
408
+ * Resolves relative file paths in provider IDs to absolute paths for consistent matching.
409
+ * Handles file://, exec:, python:, golang: prefixes and bare .js/.ts/.mjs paths.
333
410
  */
334
- async function loadFunction({ filePath, functionName, defaultFunctionName = "func", basePath = state.basePath, useCache = true }) {
335
- const cacheKey = `${filePath}${functionName ? `:${functionName}` : ""}`;
336
- if (useCache && functionCache[cacheKey]) return functionCache[cacheKey];
337
- const resolvedPath = basePath ? path.resolve(basePath, filePath) : filePath;
338
- if (!isJavascriptFile(resolvedPath) && !resolvedPath.endsWith(".py")) throw new Error(`File must be a JavaScript (${JAVASCRIPT_EXTENSIONS.join(", ")}) or Python (.py) file`);
339
- try {
340
- let func;
341
- if (isJavascriptFile(resolvedPath)) {
342
- const module = await importModule(resolvedPath, functionName);
343
- let moduleFunc;
344
- if (functionName) moduleFunc = module;
345
- else moduleFunc = typeof module === "function" ? module : module?.default?.default || module?.default || module?.[defaultFunctionName] || module;
346
- 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}")`);
347
- func = moduleFunc;
348
- } else {
349
- const result = (...args) => runPython(resolvedPath, functionName || defaultFunctionName, args);
350
- func = result;
351
- }
352
- if (useCache) functionCache[cacheKey] = func;
353
- return func;
354
- } catch (err) {
355
- logger.error(`Failed to load function: ${err.message}`);
356
- throw err;
411
+ function canonicalizeProviderId(id) {
412
+ if (id.startsWith("file://")) {
413
+ const filePath = id.slice(7);
414
+ return path.isAbsolute(filePath) ? id : `file://${path.resolve(filePath)}`;
415
+ }
416
+ for (const prefix of [
417
+ "exec:",
418
+ "python:",
419
+ "golang:"
420
+ ]) if (id.startsWith(prefix)) {
421
+ const filePath = id.slice(prefix.length);
422
+ if (filePath.includes("/") || filePath.includes("\\")) return `${prefix}${path.resolve(filePath)}`;
423
+ return id;
357
424
  }
425
+ if ((id.endsWith(".js") || id.endsWith(".ts") || id.endsWith(".mjs")) && (id.includes("/") || id.includes("\\"))) return `file://${path.resolve(id)}`;
426
+ return id;
358
427
  }
359
428
  /**
360
- * Extracts the file path and function name from a file:// URL
361
- * @param fileUrl The file:// URL (e.g., "file://path/to/file.js:functionName")
362
- * @returns The file path and optional function name
429
+ * Returns true for provider refs that should be expanded from YAML/JSON config files.
363
430
  */
364
- function parseFileUrl(fileUrl) {
365
- if (!fileUrl.startsWith("file://")) throw new Error("URL must start with file://");
366
- const urlWithoutProtocol = fileUrl.slice(7);
367
- const lastColonIndex = urlWithoutProtocol.lastIndexOf(":");
368
- if (lastColonIndex > 1) return {
369
- filePath: urlWithoutProtocol.slice(0, lastColonIndex),
370
- functionName: urlWithoutProtocol.slice(lastColonIndex + 1)
371
- };
372
- return { filePath: urlWithoutProtocol };
431
+ function isProviderConfigFileReference(providerPath) {
432
+ return providerPath.startsWith("file://") && (providerPath.endsWith(".yaml") || providerPath.endsWith(".yml") || providerPath.endsWith(".json"));
373
433
  }
374
- //#endregion
375
- //#region src/util/file.ts
376
434
  /**
377
- * Simple Nunjucks engine specifically for file paths
378
- * This function is separate from the main getNunjucksEngine to avoid circular dependencies
435
+ * Reads a provider config file and normalizes single-provider and multi-provider files.
436
+ * Returns a `wasArray` flag so callers can detect multi-provider files that require
437
+ * `loadApiProviders` instead of `loadApiProvider`.
379
438
  */
380
- function getNunjucksEngineForFilePath() {
381
- const env = nunjucks.configure({ autoescape: false });
382
- env.addGlobal("env", {
383
- ...process.env,
384
- ...state.config?.env
385
- });
386
- return env;
439
+ function readProviderConfigFile(providerPath, basePath) {
440
+ const relativePath = providerPath.slice(7);
441
+ const resolvedPath = path.isAbsolute(relativePath) ? relativePath : path.join(basePath || process.cwd(), relativePath);
442
+ let rawContent;
443
+ try {
444
+ rawContent = yaml.load(fs.readFileSync(resolvedPath, "utf8"));
445
+ } catch (err) {
446
+ throw new Error(`Failed to load provider config ${relativePath}: ${err instanceof Error ? err.message : err}`);
447
+ }
448
+ const fileContent = maybeLoadConfigFromExternalFile(rawContent);
449
+ invariant(fileContent, `Provider config ${relativePath} is undefined`);
450
+ return {
451
+ configs: [fileContent].flat(),
452
+ relativePath,
453
+ wasArray: Array.isArray(fileContent)
454
+ };
387
455
  }
388
456
  /**
389
- * Loads content from an external file if the input is a file path, otherwise
390
- * returns the input as-is. Supports Nunjucks templating for file paths.
391
- *
392
- * @param filePath - The input to process. Can be a file path string starting with "file://",
393
- * an array of file paths, or any other type of data.
394
- * @param context - Optional context to control file loading behavior. 'assertion' context
395
- * preserves Python/JS file references instead of loading their content.
396
- * @returns The loaded content if the input was a file path, otherwise the original input.
397
- * For JSON and YAML files, the content is parsed into an object.
398
- * For other file types, the raw file content is returned as a string.
399
- *
400
- * @throws {Error} If the specified file does not exist.
457
+ * Loads provider config objects from a file-backed provider reference.
401
458
  */
402
- function maybeLoadFromExternalFile(filePath, context) {
403
- if (Array.isArray(filePath)) return filePath.map((path) => {
404
- return maybeLoadFromExternalFile(path, context);
405
- });
406
- if (typeof filePath !== "string") return filePath;
407
- if (!filePath.startsWith("file://")) return filePath;
408
- const renderedFilePath = getNunjucksEngineForFilePath().renderString(filePath, {});
409
- const { filePath: cleanPath, functionName } = parseFileUrl(renderedFilePath);
410
- if (context === "assertion" && (cleanPath.endsWith(".py") || isJavascriptFile(cleanPath))) {
411
- logger.debug(`Preserving Python/JS file reference in assertion context: ${renderedFilePath}`);
412
- return renderedFilePath;
459
+ function loadProviderConfigsFromFile(providerPath, basePath) {
460
+ return readProviderConfigFile(providerPath, basePath).configs;
461
+ }
462
+ /**
463
+ * Pure, synchronous classifier that converts every supported provider reference shape
464
+ * into a discriminated descriptor. Does not read files or instantiate providers.
465
+ */
466
+ function normalizeProviderRef(provider, options = {}) {
467
+ const { index } = options;
468
+ if (typeof provider === "string") {
469
+ if (!isValidProviderId(provider)) return {
470
+ kind: "unknown",
471
+ id: index === void 0 ? "unknown" : `unknown-${index}`
472
+ };
473
+ if (isProviderConfigFileReference(provider)) return {
474
+ kind: "file",
475
+ id: provider,
476
+ loadProviderPath: provider
477
+ };
478
+ return {
479
+ kind: "named",
480
+ id: provider,
481
+ loadProviderPath: provider
482
+ };
413
483
  }
414
- if (context === "vars") {
415
- logger.debug(`Preserving file reference in vars context: ${renderedFilePath}`);
416
- return renderedFilePath;
484
+ if (typeof provider === "function") {
485
+ const label = getProviderLabel(provider);
486
+ return {
487
+ kind: "function",
488
+ id: label ?? (index === void 0 ? "custom-function" : `custom-function-${index}`),
489
+ label
490
+ };
417
491
  }
418
- if (functionName && (cleanPath.endsWith(".py") || isJavascriptFile(cleanPath))) return renderedFilePath;
419
- const pathToUse = functionName && !(cleanPath.endsWith(".py") || isJavascriptFile(cleanPath)) ? renderedFilePath.slice(7) : cleanPath;
420
- const resolvedPath = path$1.resolve(state.basePath || "", pathToUse);
421
- if (hasMagic(pathToUse)) {
422
- const matchedFiles = globSync(resolvedPath, { windowsPathsNoEscape: true });
423
- if (matchedFiles.length === 0) throw new Error(`No files found matching pattern: ${resolvedPath}`);
424
- const allContents = [];
425
- for (const matchedFile of matchedFiles) {
426
- let contents;
427
- try {
428
- contents = fs$2.readFileSync(matchedFile, "utf8");
429
- } catch (error) {
430
- if (error.code === "ENOENT") {
431
- logger.debug(`File disappeared during glob expansion: ${matchedFile}`);
432
- continue;
433
- }
434
- throw error;
492
+ if (typeof provider === "object" && provider !== null && !Array.isArray(provider)) {
493
+ const providerId = provider.id;
494
+ const label = getProviderLabel(provider);
495
+ if (isValidProviderId(providerId)) return {
496
+ kind: "options",
497
+ id: providerId,
498
+ label,
499
+ loadOptions: provider,
500
+ loadProviderPath: providerId
501
+ };
502
+ const keys = Object.keys(provider);
503
+ if (keys.length === 1 && !PROVIDER_OPTION_KEYS.has(keys[0])) {
504
+ const originalId = keys[0];
505
+ const providerObject = provider[originalId];
506
+ if (typeof providerObject === "object" && providerObject !== null && !Array.isArray(providerObject) && isValidProviderId(originalId)) {
507
+ const id = isValidProviderId(providerObject.id) ? providerObject.id : originalId;
508
+ return {
509
+ kind: "map",
510
+ id,
511
+ label: getProviderLabel(providerObject),
512
+ loadOptions: {
513
+ ...providerObject,
514
+ id
515
+ },
516
+ loadProviderPath: originalId
517
+ };
435
518
  }
436
- if (matchedFile.endsWith(".json")) {
437
- const parsed = JSON.parse(contents);
438
- if (Array.isArray(parsed)) allContents.push(...parsed);
439
- else allContents.push(parsed);
440
- } else if (matchedFile.endsWith(".yaml") || matchedFile.endsWith(".yml")) {
441
- const parsed = yaml.load(contents);
442
- if (parsed === null || parsed === void 0) continue;
443
- if (Array.isArray(parsed)) allContents.push(...parsed);
444
- else allContents.push(parsed);
445
- } else if (matchedFile.endsWith(".csv")) {
446
- const records = parse$1(contents, { columns: true });
447
- if (records.length > 0 && Object.keys(records[0]).length === 1) allContents.push(...records.map((record) => Object.values(record)[0]));
448
- else allContents.push(...records);
449
- } else allContents.push(contents);
450
519
  }
451
- return allContents;
452
- }
453
- const finalPath = resolvedPath;
454
- let contents;
455
- try {
456
- contents = fs$2.readFileSync(finalPath, "utf8");
457
- } catch (error) {
458
- if (error.code === "ENOENT") throw new Error(`File does not exist: ${finalPath}`);
459
- throw new Error(`Failed to read file ${finalPath}: ${error}`);
460
- }
461
- if (finalPath.endsWith(".json")) try {
462
- return JSON.parse(contents);
463
- } catch (error) {
464
- throw new Error(`Failed to parse JSON file ${finalPath}: ${error}`);
520
+ if (isValidProviderId(label)) return {
521
+ kind: "unknown",
522
+ id: label,
523
+ label
524
+ };
465
525
  }
466
- if (finalPath.endsWith(".yaml") || finalPath.endsWith(".yml")) try {
467
- return yaml.load(contents);
468
- } catch (error) {
469
- throw new Error(`Failed to parse YAML file ${finalPath}: ${error}`);
526
+ return {
527
+ kind: "unknown",
528
+ id: index === void 0 ? "unknown" : `unknown-${index}`
529
+ };
530
+ }
531
+ //#endregion
532
+ //#region src/util/provider.ts
533
+ function providerToIdentifier(provider) {
534
+ if (!provider) return;
535
+ if (typeof provider === "string") return canonicalizeProviderId(provider);
536
+ const { label } = normalizeProviderRef(provider);
537
+ if (label) return label;
538
+ if (isApiProvider(provider)) return canonicalizeProviderId(provider.id());
539
+ if (isProviderOptions(provider)) {
540
+ if (provider.id) return canonicalizeProviderId(provider.id);
541
+ return;
470
542
  }
471
- if (finalPath.endsWith(".csv")) {
472
- const records = parse$1(contents, { columns: true });
473
- if (records.length > 0 && Object.keys(records[0]).length === 1) return records.map((record) => Object.values(record)[0]);
474
- return records;
543
+ if (typeof provider === "object" && "id" in provider && typeof provider.id === "string") return canonicalizeProviderId(provider.id);
544
+ }
545
+ /**
546
+ * Gets a descriptive identifier string for a provider, showing both label and ID when both exist.
547
+ * Useful for error messages to help users debug provider reference issues.
548
+ */
549
+ function getProviderDescription(provider) {
550
+ const label = provider.label;
551
+ const id = provider.id();
552
+ if (label && label !== id) return `${label} (${id})`;
553
+ return id;
554
+ }
555
+ /**
556
+ * Checks if a provider reference matches a given provider.
557
+ * Supports exact matching and wildcard patterns.
558
+ */
559
+ function doesProviderRefMatch(ref, provider) {
560
+ const label = provider.label;
561
+ const id = provider.id();
562
+ const canonicalRef = canonicalizeProviderId(ref);
563
+ const canonicalId = canonicalizeProviderId(id);
564
+ if (label && label === ref) return true;
565
+ if (id === ref || canonicalId === canonicalRef) return true;
566
+ if (ref.endsWith("*")) {
567
+ const prefix = ref.slice(0, -1);
568
+ if (label?.startsWith(prefix) || id.startsWith(prefix) || canonicalId.startsWith(prefix)) return true;
475
569
  }
476
- return contents;
570
+ if (label?.startsWith(`${ref}:`) || id.startsWith(`${ref}:`) || canonicalId.startsWith(`${ref}:`)) return true;
571
+ return false;
477
572
  }
478
573
  /**
479
- * Resolves a relative file path with respect to a base path, handling cloud configuration appropriately.
480
- * When using a cloud configuration, the current working directory is always used instead of the context's base path.
481
- *
482
- * @param filePath - The relative or absolute file path to resolve.
483
- * @param isCloudConfig - Whether this is a cloud configuration.
484
- * @returns The resolved absolute file path.
574
+ * Checks if a provider is allowed based on a list of allowed references.
485
575
  */
486
- function getResolvedRelativePath(filePath, isCloudConfig) {
487
- if (path$1.isAbsolute(filePath) || !isCloudConfig) return filePath;
488
- return path$1.join(process.cwd(), filePath);
576
+ function isProviderAllowed(provider, allowedProviders) {
577
+ if (!Array.isArray(allowedProviders)) return true;
578
+ if (allowedProviders.length === 0) return false;
579
+ return allowedProviders.some((ref) => doesProviderRefMatch(ref, provider));
489
580
  }
490
581
  /**
491
- * Recursively loads external file references from a configuration object.
492
- *
493
- * @param config - The configuration object to process
494
- * @param context - Optional context to control file loading behavior
495
- * @returns The configuration with external file references resolved
582
+ * Detects if a provider uses OpenAI models.
583
+ * This includes direct OpenAI providers and Azure OpenAI.
496
584
  */
497
- function maybeLoadConfigFromExternalFile(config, context) {
498
- if (Array.isArray(config)) return config.map((item) => maybeLoadConfigFromExternalFile(item, context));
499
- if (typeof config === "object" && config !== null) {
500
- const result = {};
501
- for (const key of Object.keys(config)) {
502
- const childContext = key === "value" && "type" in config && typeof config.type === "string" && (config.type === "python" || config.type === "javascript") ? "assertion" : key === "vars" ? "vars" : context;
503
- result[key] = maybeLoadConfigFromExternalFile(config[key], childContext);
504
- }
505
- return result;
585
+ function isOpenAiProvider(providerId) {
586
+ const lowerProviderId = providerId.toLowerCase();
587
+ if (lowerProviderId.startsWith("openai:")) return true;
588
+ if (lowerProviderId.startsWith("azureopenai:")) return true;
589
+ if (lowerProviderId.startsWith("azure:")) {
590
+ if ([
591
+ "gpt",
592
+ "openai",
593
+ "davinci",
594
+ "curie",
595
+ "babbage",
596
+ "ada",
597
+ "text-embedding",
598
+ "whisper",
599
+ "dall-e",
600
+ "tts"
601
+ ].some((indicator) => lowerProviderId.includes(indicator))) return true;
506
602
  }
507
- return maybeLoadFromExternalFile(config, context);
603
+ return false;
508
604
  }
509
605
  /**
510
- * Parses a file path or glob pattern to extract function names and file extensions.
511
- * Function names can be specified in the filename like this:
512
- * prompt.py:myFunction or prompts.js:myFunction.
513
- * @param basePath - The base path for file resolution.
514
- * @param promptPath - The path or glob pattern.
515
- * @returns Parsed details including function name, file extension, and directory status.
606
+ * Detects if a provider uses Anthropic/Claude models.
607
+ * This includes direct Anthropic providers, Bedrock with Claude, and Vertex with Claude.
516
608
  */
517
- function parsePathOrGlob(basePath, promptPath) {
518
- if (promptPath.startsWith("file://")) promptPath = promptPath.slice(7);
519
- const filePath = path$1.resolve(basePath, promptPath);
520
- let filename = path$1.relative(basePath, filePath);
521
- let functionName;
522
- if (filename.includes(":")) {
523
- const lastColonIndex = filename.lastIndexOf(":");
524
- if (lastColonIndex > 1) {
525
- const pathWithoutFunction = filename.slice(0, lastColonIndex);
526
- if (isJavascriptFile(pathWithoutFunction) || pathWithoutFunction.endsWith(".py") || pathWithoutFunction.endsWith(".go") || pathWithoutFunction.endsWith(".rb")) {
527
- functionName = filename.slice(lastColonIndex + 1);
528
- filename = pathWithoutFunction;
529
- }
530
- }
609
+ function isAnthropicProvider(providerId) {
610
+ const lowerProviderId = providerId.toLowerCase();
611
+ if (lowerProviderId.startsWith("anthropic:")) return true;
612
+ if (lowerProviderId.startsWith("bedrock:")) {
613
+ if (lowerProviderId.includes("claude") || lowerProviderId.includes("anthropic")) return true;
531
614
  }
532
- let stats;
533
- try {
534
- stats = fs$2.statSync(path$1.join(basePath, filename));
535
- } catch (err) {
536
- if (getEnvBool("PROMPTFOO_STRICT_FILES")) throw err;
615
+ if (lowerProviderId.startsWith("vertex:")) {
616
+ if (lowerProviderId.includes("claude")) return true;
537
617
  }
538
- const normalizedFilePath = filePath.replace(/\\/g, "/");
539
- const isPathPattern = stats?.isDirectory() || hasMagic(promptPath) || hasMagic(normalizedFilePath);
540
- const safeFilename = path$1.relative(basePath, safeResolve(basePath, filename));
541
- return {
542
- extension: isPathPattern ? void 0 : path$1.parse(safeFilename).ext,
543
- filePath: path$1.join(basePath, safeFilename),
544
- functionName,
545
- isPathPattern
546
- };
618
+ return false;
547
619
  }
548
- function readOutput(outputPath) {
549
- const ext = path$1.parse(outputPath).ext.slice(1);
550
- switch (ext) {
551
- case "json": return JSON.parse(fs$2.readFileSync(outputPath, "utf-8"));
552
- default: throw new Error(`Unsupported output file format: ${ext} currently only supports json`);
620
+ const KNOWN_ENV_VARS = {
621
+ openai: "OPENAI_API_KEY",
622
+ anthropic: "ANTHROPIC_API_KEY",
623
+ google: "GOOGLE_API_KEY",
624
+ mistral: "MISTRAL_API_KEY",
625
+ cohere: "COHERE_API_KEY",
626
+ replicate: "REPLICATE_API_TOKEN",
627
+ voyage: "VOYAGE_API_KEY",
628
+ ai21: "AI21_API_KEY",
629
+ xai: "XAI_API_KEY",
630
+ groq: "GROQ_API_KEY",
631
+ deepseek: "DEEPSEEK_API_KEY",
632
+ perplexity: "PERPLEXITY_API_KEY",
633
+ hyperbolic: "HYPERBOLIC_API_KEY",
634
+ cerebras: "CEREBRAS_API_KEY",
635
+ togetherai: "TOGETHER_API_KEY",
636
+ fal: "FAL_KEY",
637
+ huggingface: "HF_TOKEN",
638
+ "cloudflare-ai": "CLOUDFLARE_API_KEY"
639
+ };
640
+ function getDefaultEnvVar(providerId) {
641
+ const prefix = providerId.split(":")[0];
642
+ return KNOWN_ENV_VARS[prefix] || `${prefix.toUpperCase()}_API_KEY`;
643
+ }
644
+ /**
645
+ * Pre-checks providers for missing API keys before evaluation starts.
646
+ * Assumes getApiKey() is side-effect free (no network calls or token refresh).
647
+ */
648
+ function checkProviderApiKeys(providers) {
649
+ const missingApiKeys = /* @__PURE__ */ new Map();
650
+ for (const provider of providers) {
651
+ const p = provider;
652
+ if (typeof p.getApiKey !== "function") continue;
653
+ if (provider.id().startsWith("azure:")) continue;
654
+ const requiresKey = typeof p.requiresApiKey === "function" ? p.requiresApiKey() : p.config?.apiKeyRequired !== false;
655
+ let apiKey;
656
+ try {
657
+ apiKey = p.getApiKey();
658
+ } catch {
659
+ apiKey = void 0;
660
+ }
661
+ if (requiresKey && !apiKey) {
662
+ const envVar = p.config?.apiKeyEnvar || getDefaultEnvVar(provider.id());
663
+ if (!missingApiKeys.has(envVar)) missingApiKeys.set(envVar, []);
664
+ missingApiKeys.get(envVar).push(provider.id());
665
+ }
553
666
  }
667
+ return missingApiKeys;
554
668
  }
555
669
  /**
556
- * Load custom Nunjucks filters from external files.
557
- * Note: If a glob pattern matches multiple files, only the last file's export is used.
558
- * Each filter name should typically resolve to a single file.
670
+ * Detects if a provider uses Google models.
671
+ * This includes direct Google/Vertex providers with Gemini and other Google models.
672
+ * Note: Vertex with Claude models is NOT counted as Google (it's Anthropic).
559
673
  */
560
- async function readFilters(filters, basePath = "") {
561
- const ret = {};
562
- for (const [name, filterPath] of Object.entries(filters)) {
563
- const filePaths = globSync(path$1.join(basePath, filterPath), { windowsPathsNoEscape: true });
564
- for (const filePath of filePaths) ret[name] = await importModule(path$1.resolve(filePath));
674
+ function isGoogleProvider(providerId) {
675
+ const lowerProviderId = providerId.toLowerCase();
676
+ if (lowerProviderId.startsWith("google:")) return true;
677
+ if (lowerProviderId.startsWith("vertex:")) {
678
+ if (!lowerProviderId.includes("claude")) return true;
565
679
  }
566
- return ret;
680
+ return false;
567
681
  }
682
+ //#endregion
683
+ //#region src/util/comparison.ts
568
684
  /**
569
- * Loads configuration from an external file with variable rendering.
570
- * This is a convenience wrapper that combines renderVarsInObject and maybeLoadFromExternalFile.
685
+ * Explicit runtime variable names that don't follow the underscore convention.
686
+ * These are added during evaluation but aren't part of the original test definition.
571
687
  *
572
- * Use this for simple config fields that:
573
- * - Need variable rendering ({{ vars.x }}, {{ env.X }})
574
- * - May reference external files (file://path.json)
575
- * - Don't have nested file references that need loading
688
+ * - sessionId: Added by multi-turn strategy providers (GOAT, Crescendo)
576
689
  *
577
- * For fields with nested file references (like response_format.schema),
578
- * use maybeLoadResponseFormatFromExternalFile instead.
690
+ * Note: Variables starting with underscore (e.g., _conversation) are automatically
691
+ * treated as runtime variables and filtered out.
692
+ */
693
+ const EXPLICIT_RUNTIME_VAR_KEYS = ["sessionId"];
694
+ /**
695
+ * Checks if a variable key is a runtime-only variable that should be filtered
696
+ * when comparing test cases.
579
697
  *
580
- * @param config - The configuration to process
581
- * @param vars - Variables for template rendering
582
- * @returns The processed configuration with variables rendered and files loaded
698
+ * Runtime variables are identified by:
699
+ * 1. Starting with underscore (_) - convention for internal/runtime vars
700
+ * 2. Being in the explicit runtime var list (for legacy vars like sessionId)
583
701
  */
584
- function maybeLoadFromExternalFileWithVars(config, vars) {
585
- return maybeLoadFromExternalFile(renderVarsInObject(config, vars));
702
+ function isRuntimeVar(key) {
703
+ return key.startsWith("_") || EXPLICIT_RUNTIME_VAR_KEYS.includes(key);
586
704
  }
587
705
  /**
588
- * Loads response_format configuration from an external file with variable rendering.
706
+ * Filters out runtime-only variables that are added during evaluation
707
+ * but aren't part of the original test definition.
589
708
  *
590
- * This function handles the special case where response_format may contain:
591
- * 1. A top-level file reference (file://format.json)
592
- * 2. A nested schema reference for json_schema type (schema: file://schema.json)
709
+ * This is used when comparing test cases to determine if a result
710
+ * corresponds to a particular test, regardless of runtime state.
593
711
  *
594
- * Both levels need variable rendering and file loading.
712
+ * Runtime variables are identified by:
713
+ * - Starting with underscore (e.g., _conversation, _metadata)
714
+ * - Being in the explicit list (e.g., sessionId for backward compatibility)
715
+ */
716
+ function filterRuntimeVars(vars) {
717
+ if (!vars || typeof vars !== "object" || Array.isArray(vars)) return vars;
718
+ const filtered = {};
719
+ for (const [key, value] of Object.entries(vars)) if (!isRuntimeVar(key)) filtered[key] = value;
720
+ return filtered;
721
+ }
722
+ /**
723
+ * Extracts only runtime variables from a vars object.
724
+ * This is the inverse of filterRuntimeVars.
595
725
  *
596
- * @param responseFormat - The response_format configuration
597
- * @param vars - Variables for template rendering
598
- * @returns The processed response_format with all files loaded
726
+ * Used to restore runtime state when re-running filtered tests.
599
727
  */
600
- function maybeLoadResponseFormatFromExternalFile(responseFormat, vars) {
601
- if (responseFormat === void 0 || responseFormat === null) return responseFormat;
602
- const loaded = maybeLoadFromExternalFile(renderVarsInObject(responseFormat, vars));
603
- if (!loaded || typeof loaded !== "object") return loaded;
604
- if (loaded.type === "json_schema") {
605
- const nestedSchema = loaded.schema || loaded.json_schema?.schema;
606
- if (nestedSchema) {
607
- const loadedSchema = maybeLoadFromExternalFile(renderVarsInObject(nestedSchema, vars));
608
- if (loaded.schema !== void 0) return {
609
- ...loaded,
610
- schema: loadedSchema
611
- };
612
- else if (loaded.json_schema?.schema !== void 0) return {
613
- ...loaded,
614
- json_schema: {
615
- ...loaded.json_schema,
616
- schema: loadedSchema
617
- }
618
- };
619
- }
620
- }
621
- return loaded;
728
+ function extractRuntimeVars(vars) {
729
+ if (!vars || typeof vars !== "object" || Array.isArray(vars)) return;
730
+ const extracted = {};
731
+ for (const [key, value] of Object.entries(vars)) if (isRuntimeVar(key)) extracted[key] = value;
732
+ return Object.keys(extracted).length > 0 ? extracted : void 0;
733
+ }
734
+ function varsMatch(vars1, vars2) {
735
+ return deepEqual(vars1, vars2);
622
736
  }
623
737
  /**
624
- * Renders variables in a tools object and loads from external file if applicable.
625
- * This function combines renderVarsInObject and maybeLoadFromExternalFile into a single step
626
- * specifically for handling tools configurations.
738
+ * Generate a unique key for a test case for deduplication purposes.
739
+ * Excludes runtime variables and includes strategyId to distinguish tests
740
+ * with the same prompt but different strategies.
627
741
  *
628
- * Supports loading from JSON, YAML, Python, and JavaScript files.
742
+ * @param testCase - The test case to generate a key for
743
+ * @returns A JSON string that uniquely identifies the test case
744
+ */
745
+ function getTestCaseDeduplicationKey(testCase) {
746
+ const filteredVars = filterRuntimeVars(testCase.vars);
747
+ const strategyId = testCase.metadata?.strategyId || "none";
748
+ return JSON.stringify({
749
+ vars: filteredVars,
750
+ strategyId
751
+ });
752
+ }
753
+ /**
754
+ * Deduplicates an array of test cases based on their vars and strategyId.
755
+ * Tests with the same vars but different strategies are considered different.
756
+ * Runtime variables (like _conversation, sessionId) are filtered out before comparison.
629
757
  *
630
- * @param tools - The tools configuration object or array to process.
631
- * @param vars - Variables to use for rendering.
632
- * @returns The processed tools configuration with variables rendered and content loaded from files if needed.
633
- * @throws {Error} If the loaded tools are in an invalid format
758
+ * @param tests - Array of test cases to deduplicate
759
+ * @returns Deduplicated array of test cases
634
760
  */
635
- async function maybeLoadToolsFromExternalFile(tools, vars) {
636
- const rendered = renderVarsInObject(tools, vars);
637
- if (typeof rendered === "string" && rendered.startsWith("file://")) {
638
- const { filePath, functionName } = parseFileUrl(rendered);
639
- if (functionName && (filePath.endsWith(".py") || isJavascriptFile(filePath))) {
640
- const fileType = filePath.endsWith(".py") ? "Python" : "JavaScript";
641
- logger.debug(`[maybeLoadToolsFromExternalFile] Loading tools from ${fileType} file: ${filePath}:${functionName}`);
642
- try {
643
- let toolDefinitions;
644
- if (filePath.endsWith(".py")) {
645
- const absPath = safeResolve(state.basePath || process.cwd(), filePath);
646
- logger.debug(`[maybeLoadToolsFromExternalFile] Resolved Python path: ${absPath}`);
647
- toolDefinitions = await runPython(absPath, functionName, []);
648
- } else {
649
- const absPath = safeResolve(state.basePath || process.cwd(), filePath);
650
- logger.debug(`[maybeLoadToolsFromExternalFile] Resolved JavaScript path: ${absPath}`);
651
- const module = await importModule(absPath);
652
- const fn = module[functionName] || module.default?.[functionName];
653
- if (typeof fn !== "function") {
654
- const availableExports = Object.keys(module).filter((k) => k !== "default");
655
- const basePath = state.basePath || process.cwd();
656
- throw new Error(`Function "${functionName}" not found in ${filePath}. Available exports: ${availableExports.length > 0 ? availableExports.join(", ") : "(none)"}\nResolved from: ${basePath}`);
657
- }
658
- toolDefinitions = await Promise.resolve(fn());
659
- }
660
- 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}`);
661
- logger.debug(`[maybeLoadToolsFromExternalFile] Successfully loaded ${Array.isArray(toolDefinitions) ? toolDefinitions.length : "object"} tools`);
662
- return toolDefinitions;
663
- } catch (err) {
664
- const errorMessage = err instanceof Error ? err.message : String(err);
665
- const basePath = state.basePath || process.cwd();
666
- 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}`);
667
- }
668
- }
669
- if (filePath.endsWith(".py") || isJavascriptFile(filePath)) {
670
- const ext = filePath.endsWith(".py") ? "Python" : "JavaScript";
671
- const basePath = state.basePath || process.cwd();
672
- 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}`);
673
- }
674
- }
675
- if (Array.isArray(rendered)) {
676
- const results = await Promise.all(rendered.map((item) => maybeLoadToolsFromExternalFile(item, vars)));
677
- if (results.every((r) => Array.isArray(r))) return results.flat();
678
- return results;
679
- }
680
- if (typeof rendered !== "string") return rendered;
681
- const loaded = maybeLoadFromExternalFile(rendered);
682
- if (loaded !== void 0 && loaded !== null && typeof loaded === "string") {
683
- 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.`);
684
- 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");
685
- 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.");
761
+ function deduplicateTestCases(tests) {
762
+ const seen = /* @__PURE__ */ new Set();
763
+ return tests.filter((test) => {
764
+ const key = getTestCaseDeduplicationKey(test);
765
+ if (seen.has(key)) return false;
766
+ seen.add(key);
767
+ return true;
768
+ });
769
+ }
770
+ function resultIsForTestCase(result, testCase) {
771
+ const testProviderId = testCase.provider ? providerToIdentifier(testCase.provider) : void 0;
772
+ const resultProviderId = providerToIdentifier(result.provider);
773
+ const providersMatch = !testProviderId || !resultProviderId || testProviderId === resultProviderId;
774
+ const resultVars = filterRuntimeVars(result.vars);
775
+ const testVars = filterRuntimeVars(testCase.vars);
776
+ const doVarsMatch = varsMatch(testVars, resultVars);
777
+ const isMatch = doVarsMatch && providersMatch;
778
+ if (!isMatch) {
779
+ const varKeys = testVars ? Object.keys(testVars).join(", ") : "none";
780
+ logger.debug(`[resultIsForTestCase] No match: vars=${doVarsMatch}, providers=${providersMatch}`, {
781
+ testProvider: testProviderId || "none",
782
+ resultProvider: resultProviderId || "none",
783
+ testVarKeys: varKeys
784
+ });
686
785
  }
687
- return loaded;
786
+ return isMatch;
787
+ }
788
+ //#endregion
789
+ //#region src/util/env.ts
790
+ /**
791
+ * Load environment variables from .env file(s).
792
+ * @param envPath - Single path, array of paths, or undefined for default .env loading.
793
+ * When paths are explicitly specified, all files must exist or an error is thrown.
794
+ * When multiple files are provided, later files override values from earlier files.
795
+ */
796
+ function setupEnv(envPath) {
797
+ if (envPath) {
798
+ const paths = (Array.isArray(envPath) ? envPath : [envPath]).flatMap((p) => p.includes(",") ? p.split(",").map((s) => s.trim()) : p.trim()).filter((p) => p.length > 0);
799
+ if (paths.length === 0) {
800
+ dotenv.config({ quiet: true });
801
+ return;
802
+ }
803
+ for (const p of paths) if (!fs$2.existsSync(p)) throw new Error(`Environment file not found: ${p}`);
804
+ if (paths.length === 1) logger.info(`Loading environment variables from ${paths[0]}`);
805
+ else logger.info(`Loading environment variables from: ${paths.join(", ")}`);
806
+ const pathArg = paths.length === 1 ? paths[0] : paths;
807
+ dotenv.config({
808
+ path: pathArg,
809
+ override: true,
810
+ quiet: true
811
+ });
812
+ } else dotenv.config({ quiet: true });
688
813
  }
689
814
  //#endregion
690
815
  //#region src/googleSheets.ts
@@ -1299,6 +1424,6 @@ function printBorder() {
1299
1424
  logger.info(border);
1300
1425
  }
1301
1426
  //#endregion
1302
- export { getProviderDescription as A, deduplicateTestCases as C, resultIsForTestCase as D, getTestCaseDeduplicationKey as E, isGoogleProvider as M, isOpenAiProvider as N, checkProviderApiKeys as O, isProviderAllowed as P, setupEnv as S, filterRuntimeVars as T, parsePathOrGlob as _, ComparisonEvalNotFoundError as a, loadFunction as b, mergeComparisonTables as c, getResolvedRelativePath as d, maybeLoadConfigFromExternalFile as f, maybeLoadToolsFromExternalFile as g, maybeLoadResponseFormatFromExternalFile as h, writeOutput as i, isAnthropicProvider as j, doesProviderRefMatch as k, fetchCsvFromGoogleSheet as l, maybeLoadFromExternalFileWithVars as m, createOutputMetadata as n, evalTableToJson as o, maybeLoadFromExternalFile as p, writeMultipleOutputs as r, generateEvalCsv as s, printBorder as t, getNunjucksEngineForFilePath as u, readFilters as v, extractRuntimeVars as w, parseFileUrl as x, readOutput as y };
1427
+ export { maybeLoadFromExternalFile as A, isProviderConfigFileReference as C, getNunjucksEngineForFilePath as D, readProviderConfigFile as E, readFilters as F, readOutput as I, loadFunction as L, maybeLoadResponseFormatFromExternalFile as M, maybeLoadToolsFromExternalFile as N, getResolvedRelativePath as O, parsePathOrGlob as P, parseFileUrl as R, isProviderAllowed as S, normalizeProviderRef as T, doesProviderRefMatch as _, ComparisonEvalNotFoundError as a, isGoogleProvider as b, mergeComparisonTables as c, deduplicateTestCases as d, extractRuntimeVars as f, checkProviderApiKeys as g, resultIsForTestCase as h, writeOutput as i, maybeLoadFromExternalFileWithVars as j, maybeLoadConfigFromExternalFile as k, fetchCsvFromGoogleSheet as l, getTestCaseDeduplicationKey as m, createOutputMetadata as n, evalTableToJson as o, filterRuntimeVars as p, writeMultipleOutputs as r, generateEvalCsv as s, printBorder as t, setupEnv as u, getProviderDescription as v, loadProviderConfigsFromFile as w, isOpenAiProvider as x, isAnthropicProvider as y };
1303
1428
 
1304
- //# sourceMappingURL=util-DxWpWjhc.js.map
1429
+ //# sourceMappingURL=util-BQOCAHQC.js.map