@langwatch/mcp-server 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (430) hide show
  1. package/.gitignore +7 -0
  2. package/.npmignore +41 -0
  3. package/CHANGELOG.md +280 -200
  4. package/CONTRIBUTING.md +10 -0
  5. package/README.md +82 -52
  6. package/dist/add-dashboard-widget-4ZC5UN73.js +50 -0
  7. package/dist/add-dashboard-widget-ACC6QYBG.js +49 -0
  8. package/dist/archive-project-KWFM3I5Z.js +19 -0
  9. package/dist/{archive-project-4EQGLBJJ.js → archive-project-SPVHXK6A.js} +4 -4
  10. package/dist/archive-run-plan-65QN3XRX.js +16 -0
  11. package/dist/archive-run-plan-Z4SYE6BP.js +15 -0
  12. package/dist/{archive-scenario-W6BXYDZ3.js → archive-scenario-A6QV5LOW.js} +4 -4
  13. package/dist/archive-scenario-FXEYQNEH.js +19 -0
  14. package/dist/archive-test-suite-5ADQ3KPQ.js +15 -0
  15. package/dist/archive-test-suite-NKZDZY3D.js +16 -0
  16. package/dist/assign-prompt-tag-5IUZXJ2T.js +23 -0
  17. package/dist/{assign-prompt-tag-VLXUWEKU.js → assign-prompt-tag-HBZLBIFD.js} +8 -4
  18. package/dist/{chunk-CDYQOY5U.js → chunk-3OCG3QPU.js} +7 -10
  19. package/dist/chunk-3YLN3ATJ.js +241 -0
  20. package/dist/chunk-5FNCQRCX.js +91 -0
  21. package/dist/chunk-6YZUOD34.js +55 -0
  22. package/dist/{chunk-WVI5CJ6S.js → chunk-7AQLPVFL.js} +2 -2
  23. package/dist/chunk-7VNSRCXO.js +129 -0
  24. package/dist/{chunk-7RHG56OH.js → chunk-B4J3PJLX.js} +11 -10
  25. package/dist/{chunk-ZXKLPC2E.js → chunk-BIIKE3EI.js} +1 -1
  26. package/dist/{chunk-37SMPMQS.js → chunk-DMM3JQ42.js} +7 -2
  27. package/dist/{chunk-NUFPTHKU.js → chunk-FOKVY3BK.js} +1 -1
  28. package/dist/{chunk-WT4PO4A3.js → chunk-H6WGMG2T.js} +10 -7
  29. package/dist/{chunk-TOXJQDIZ.js → chunk-HGELNIVY.js} +135 -51
  30. package/dist/{chunk-UJPXFA26.js → chunk-HHMNZAPD.js} +101 -74
  31. package/dist/{chunk-J42FWRJ2.js → chunk-I7ATIZED.js} +7 -7
  32. package/dist/{chunk-AQJO2EI5.js → chunk-ILZDO25L.js} +7 -10
  33. package/dist/{chunk-4QC7SA2L.js → chunk-J7PQKBLD.js} +884 -559
  34. package/dist/{chunk-YX3A3CFM.js → chunk-JRJPH75H.js} +7 -13
  35. package/dist/chunk-K2I7Z3LZ.js +28 -0
  36. package/dist/{chunk-5CCZFUXS.js → chunk-NKMTIHIJ.js} +6 -9
  37. package/dist/chunk-O7EMWEVJ.js +44 -0
  38. package/dist/chunk-PTDR56Z3.js +37 -0
  39. package/dist/chunk-RABYBFX5.js +43 -0
  40. package/dist/chunk-SUPUUC4W.js +64 -0
  41. package/dist/{chunk-SFB4MU6H.js → chunk-SXFC3FNH.js} +4 -4
  42. package/dist/chunk-U24EACC4.js +67 -0
  43. package/dist/chunk-UNJM3BIL.js +21 -0
  44. package/dist/{chunk-ZWIL4EFL.js → chunk-UTJCWXPZ.js} +4 -4
  45. package/dist/chunk-UWLE5T2F.js +312 -0
  46. package/dist/chunk-VMWJD732.js +57 -0
  47. package/dist/chunk-XUIJYNRJ.js +15 -0
  48. package/dist/chunk-Y34DHR5K.js +67 -0
  49. package/dist/chunk-YCARO42Z.js +516 -0
  50. package/dist/config-EAKW6F26.js +16 -0
  51. package/dist/config.cjs +109 -0
  52. package/dist/config.js +5 -4
  53. package/dist/{create-agent-7XHO6UIS.js → create-agent-AYZ3LB3V.js} +4 -4
  54. package/dist/create-agent-S5ZBCEFT.js +23 -0
  55. package/dist/create-annotation-LHNCLHYP.js +29 -0
  56. package/dist/{create-annotation-YQXRQC6A.js → create-annotation-VUSXULQK.js} +10 -5
  57. package/dist/{create-api-key-YUS36KBU.js → create-api-key-NNYKARMG.js} +5 -7
  58. package/dist/create-api-key-T4KSNPKB.js +24 -0
  59. package/dist/create-dashboard-DHVFMEU4.js +18 -0
  60. package/dist/{create-dashboard-4Z5JO5LB.js → create-dashboard-WTKKS5Z6.js} +4 -4
  61. package/dist/{create-dataset-E6NMSVX7.js → create-dataset-2MC4SD4O.js} +6 -6
  62. package/dist/create-dataset-LUP5PVDM.js +22 -0
  63. package/dist/create-dataset-records-NSKBZP3G.js +20 -0
  64. package/dist/{create-dataset-records-SKHPRD2K.js → create-dataset-records-W4GLN2FV.js} +5 -7
  65. package/dist/{create-evaluator-JXF5PF33.js → create-evaluator-643G6LXL.js} +4 -4
  66. package/dist/create-evaluator-HMTYO5SS.js +27 -0
  67. package/dist/create-mcp-server.cjs +9513 -0
  68. package/dist/create-mcp-server.js +2647 -5
  69. package/dist/create-project-6N7PIYRY.js +44 -0
  70. package/dist/{create-project-CS2CB57Y.js → create-project-KAIC46UY.js} +4 -4
  71. package/dist/{create-prompt-F5RMX3L3.js → create-prompt-E67LXIYT.js} +5 -7
  72. package/dist/create-prompt-PKJ7EDRR.js +37 -0
  73. package/dist/create-prompt-tag-4YO3FSJ3.js +17 -0
  74. package/dist/{create-prompt-tag-GMRWTEFR.js → create-prompt-tag-OPQUEXWB.js} +3 -3
  75. package/dist/create-scenario-5PJBMAXO.js +33 -0
  76. package/dist/{create-scenario-JDR2VB2H.js → create-scenario-UEM77OE7.js} +11 -4
  77. package/dist/create-test-suite-4IPXVSFQ.js +37 -0
  78. package/dist/create-test-suite-SZEPVXSE.js +36 -0
  79. package/dist/create-trigger-5HZSCRAN.js +49 -0
  80. package/dist/create-trigger-PRELMT6J.js +48 -0
  81. package/dist/delete-agent-JUIZWAXG.js +18 -0
  82. package/dist/{delete-agent-O3EEH4S5.js → delete-agent-QMEK3XHK.js} +4 -4
  83. package/dist/delete-annotation-HY3O2OGR.js +17 -0
  84. package/dist/{delete-annotation-XP6FQM7A.js → delete-annotation-VKIQWUYW.js} +4 -4
  85. package/dist/delete-dashboard-76N66XXL.js +18 -0
  86. package/dist/{delete-dashboard-RFUNYCNP.js → delete-dashboard-JVIVHNJM.js} +4 -4
  87. package/dist/delete-dataset-HQCR66MG.js +18 -0
  88. package/dist/{delete-dataset-3MJFRDLP.js → delete-dataset-WKRUW4FP.js} +5 -7
  89. package/dist/{delete-dataset-records-WPMU5GL5.js → delete-dataset-records-C2IQFFVQ.js} +5 -7
  90. package/dist/delete-dataset-records-SDUHQLLM.js +19 -0
  91. package/dist/delete-evaluator-4SNSCLEE.js +16 -0
  92. package/dist/delete-evaluator-QP3QHTLN.js +15 -0
  93. package/dist/delete-prompt-tag-NZ4JQMYG.js +17 -0
  94. package/dist/{delete-prompt-tag-KNMZVSPB.js → delete-prompt-tag-YYQ4VMAD.js} +3 -3
  95. package/dist/{delete-workflow-3NUXO46U.js → delete-workflow-5J46T73T.js} +4 -4
  96. package/dist/delete-workflow-JSWABL5F.js +17 -0
  97. package/dist/discover-evaluator-schema-UHWA5HVL.js +120 -0
  98. package/dist/discover-evaluator-schema-XKCLLCB3.js +118 -0
  99. package/dist/discover-scenario-schema-NOJY6S7E.js +137 -0
  100. package/dist/discover-scenario-schema-OOFIQKOG.js +139 -0
  101. package/dist/{discover-schema-L6QDPON3.js → discover-schema-55YJBFGQ.js} +181 -186
  102. package/dist/discover-schema-A33SLMXW.js +442 -0
  103. package/dist/get-agent-LJQX7OF4.js +69 -0
  104. package/dist/get-agent-OTIYUFZA.js +70 -0
  105. package/dist/get-analytics-DCS72353.js +80 -0
  106. package/dist/get-analytics-IQZKJHLW.js +79 -0
  107. package/dist/get-dashboard-5NYJEIX2.js +29 -0
  108. package/dist/{get-dashboard-KHBEGZGL.js → get-dashboard-QMTYDQAZ.js} +4 -4
  109. package/dist/get-dataset-ALSP7RTX.js +64 -0
  110. package/dist/get-dataset-X4B5JDG2.js +63 -0
  111. package/dist/{get-evaluator-ADMKVWYZ.js → get-evaluator-ISMC4LLI.js} +4 -4
  112. package/dist/get-evaluator-XEUZGZCH.js +47 -0
  113. package/dist/{get-project-RAK7URQJ.js → get-project-5VKROSDH.js} +4 -4
  114. package/dist/get-project-RM6FRCBK.js +26 -0
  115. package/dist/get-prompt-OWXJZHQQ.js +95 -0
  116. package/dist/get-prompt-W52DY36E.js +94 -0
  117. package/dist/get-run-plan-BE3M2AUX.js +59 -0
  118. package/dist/get-run-plan-RUXQ6PAX.js +58 -0
  119. package/dist/get-scenario-RNM7BAX2.js +37 -0
  120. package/dist/{get-scenario-ZTAJJL5T.js → get-scenario-YM4ODWW6.js} +8 -4
  121. package/dist/get-simulation-run-6GIATQE5.js +69 -0
  122. package/dist/{get-simulation-run-T2ZQWYH2.js → get-simulation-run-LCFSMPGQ.js} +33 -20
  123. package/dist/get-test-suite-GC2O7YH6.js +49 -0
  124. package/dist/get-test-suite-P5LJ6VLF.js +48 -0
  125. package/dist/get-trace-KBRVV4NN.js +45 -0
  126. package/dist/{get-trace-K7U234Q4.js → get-trace-SEF3FMR5.js} +11 -13
  127. package/dist/get-workflow-7PVED7JE.js +25 -0
  128. package/dist/{get-workflow-L5L2PSK3.js → get-workflow-YXULW4GC.js} +4 -4
  129. package/dist/{http-security-EU3CHTY7.js → http-security-DNLBNQZL.js} +2 -2
  130. package/dist/{http-server-YXHFPPGA.js → http-server-IR4QOS43.js} +4071 -342
  131. package/dist/index.js +60221 -38077
  132. package/dist/langwatch-api-annotations-6SA4OB4C.js +17 -0
  133. package/dist/{langwatch-api-annotations-FDAS6NQ7.js → langwatch-api-annotations-TBHLCQ5R.js} +4 -4
  134. package/dist/langwatch-api-dashboards-6EO2VVZW.js +17 -0
  135. package/dist/{langwatch-api-dashboards-JTVCS7ZJ.js → langwatch-api-dashboards-NHU5BZD7.js} +4 -4
  136. package/dist/langwatch-api-monitors-CBF3FGIM.js +47 -0
  137. package/dist/{langwatch-api-monitors-3J7UIQME.js → langwatch-api-monitors-YNWA2M5Y.js} +11 -16
  138. package/dist/langwatch-api-secrets-ADTOJELP.js +56 -0
  139. package/dist/langwatch-api-secrets-RNFO4LF2.js +55 -0
  140. package/dist/langwatch-api-triggers-KQDQC6L5.js +22 -0
  141. package/dist/langwatch-api-triggers-QS7HDIVW.js +23 -0
  142. package/dist/{langwatch-api-workflows-NBJX5KOG.js → langwatch-api-workflows-OKC4FXIF.js} +4 -4
  143. package/dist/langwatch-api-workflows-XGJE6SR5.js +15 -0
  144. package/dist/list-agents-HVYWEQMI.js +43 -0
  145. package/dist/list-agents-RBAOCF53.js +42 -0
  146. package/dist/list-annotations-HJH4NBLD.js +38 -0
  147. package/dist/{list-annotations-ZTVP4EEL.js → list-annotations-J4UAHSUN.js} +10 -5
  148. package/dist/{list-api-keys-CWD4UQT5.js → list-api-keys-7UTJRZDG.js} +10 -5
  149. package/dist/list-api-keys-A2PMV435.js +42 -0
  150. package/dist/list-dashboard-boards-OPWROHMG.js +33 -0
  151. package/dist/list-dashboard-boards-U2GJ7GC5.js +34 -0
  152. package/dist/{list-dashboards-PWG6P6KR.js → list-dashboards-ROSYFLDQ.js} +4 -4
  153. package/dist/list-dashboards-RQ635ZU7.js +32 -0
  154. package/dist/{list-dataset-records-IOE7LQXS.js → list-dataset-records-M5TKA6LK.js} +14 -10
  155. package/dist/list-dataset-records-WFM7F33C.js +43 -0
  156. package/dist/{list-datasets-ZT4JYUDT.js → list-datasets-B5X4P7BQ.js} +9 -13
  157. package/dist/list-datasets-THUKQHPX.js +56 -0
  158. package/dist/{list-evaluators-SJHC7VBH.js → list-evaluators-MYH2OVCI.js} +5 -7
  159. package/dist/list-evaluators-NLVWF6K7.js +32 -0
  160. package/dist/{list-model-providers-PPD2WPD2.js → list-model-providers-3JUGP4YI.js} +4 -4
  161. package/dist/list-model-providers-H3XKORFO.js +35 -0
  162. package/dist/{list-projects-K7QAIX2I.js → list-projects-6EJ5YYEW.js} +5 -7
  163. package/dist/list-projects-YNNSRMLC.js +40 -0
  164. package/dist/{list-prompt-tags-FOO6TNHB.js → list-prompt-tags-NVFQO6WX.js} +3 -3
  165. package/dist/list-prompt-tags-XUQTTAH4.js +23 -0
  166. package/dist/{list-prompts-SC2HAUFG.js → list-prompts-HSIMMHTF.js} +4 -6
  167. package/dist/list-prompts-IRI6AIXO.js +30 -0
  168. package/dist/list-run-plans-PX2XI6EK.js +46 -0
  169. package/dist/list-run-plans-QALLCN2K.js +45 -0
  170. package/dist/list-scenarios-QZRPVHS4.js +46 -0
  171. package/dist/list-scenarios-SSJQEMJP.js +45 -0
  172. package/dist/list-simulation-runs-KNOLGMDV.js +53 -0
  173. package/dist/{list-simulation-runs-KF5THG5H.js → list-simulation-runs-OEADLLQR.js} +19 -9
  174. package/dist/list-test-suites-ANAQBLDG.js +34 -0
  175. package/dist/list-test-suites-N4WVLBKK.js +35 -0
  176. package/dist/list-trigger-fires-JV3WFYYG.js +19 -0
  177. package/dist/list-trigger-fires-WTW4DVIW.js +20 -0
  178. package/dist/list-triggers-SQNIG35Q.js +45 -0
  179. package/dist/list-triggers-V4Y356AK.js +46 -0
  180. package/dist/{list-workflows-QSJWURCK.js → list-workflows-IMSX7SXU.js} +5 -7
  181. package/dist/list-workflows-ZHTX5LCL.js +33 -0
  182. package/dist/{rename-prompt-tag-EKWU5QF3.js → rename-prompt-tag-IQI7MHOI.js} +3 -3
  183. package/dist/rename-prompt-tag-PA2BRIE6.js +18 -0
  184. package/dist/rename-test-suite-4J7UX5S2.js +21 -0
  185. package/dist/rename-test-suite-NNVLMLKY.js +20 -0
  186. package/dist/report-issue-AS34ZMJF.js +898 -0
  187. package/dist/report-issue-DCZFLGDO.js +899 -0
  188. package/dist/rerun-run-plan-TG2V4WM4.js +22 -0
  189. package/dist/rerun-run-plan-ZI5OY3VA.js +21 -0
  190. package/dist/revoke-api-key-3J3ZA4UB.js +15 -0
  191. package/dist/{revoke-api-key-FTRTZMOF.js → revoke-api-key-SFMEFWJC.js} +4 -4
  192. package/dist/run-agent-W4VJP72Z.js +75 -0
  193. package/dist/run-agent-YNV6ZHW4.js +74 -0
  194. package/dist/run-plan-6C77GWG4.js +38 -0
  195. package/dist/run-plan-PAARQSI4.js +39 -0
  196. package/dist/run-query-KJDH544C.js +47 -0
  197. package/dist/run-query-T4QW2Z5O.js +46 -0
  198. package/dist/run-test-suite-SQJELEZX.js +26 -0
  199. package/dist/run-test-suite-STB6NUJM.js +25 -0
  200. package/dist/{run-workflow-QPXC627O.js → run-workflow-D6PQNBI6.js} +4 -4
  201. package/dist/{run-agent-6SM4XNA7.js → run-workflow-SP32SO5M.js} +10 -11
  202. package/dist/search-traces-HCBGOG6R.js +170 -0
  203. package/dist/search-traces-HSTQ6WTQ.js +171 -0
  204. package/dist/{set-model-provider-3WKT63ZN.js → set-model-provider-GF7NXUJA.js} +4 -4
  205. package/dist/set-model-provider-KDQ7C4EZ.js +33 -0
  206. package/dist/{update-agent-SMAEHLXH.js → update-agent-DWG43T2P.js} +4 -4
  207. package/dist/update-agent-MXNOBS4H.js +20 -0
  208. package/dist/update-dashboard-widget-CJKJLCDY.js +71 -0
  209. package/dist/update-dashboard-widget-CYQDS2NR.js +70 -0
  210. package/dist/update-dataset-DQH3OHOU.js +22 -0
  211. package/dist/{update-dataset-BDUK7TUO.js → update-dataset-SQZ6T4WX.js} +6 -6
  212. package/dist/{update-dataset-record-4ZSDRBSW.js → update-dataset-record-3AT4IPBC.js} +4 -4
  213. package/dist/update-dataset-record-7PLDGTMR.js +19 -0
  214. package/dist/update-evaluator-4LFTKFPT.js +24 -0
  215. package/dist/{update-evaluator-LS6TIBY7.js → update-evaluator-IGTX6PX4.js} +4 -4
  216. package/dist/{update-project-LKVYXENH.js → update-project-PLJIOZS6.js} +4 -4
  217. package/dist/update-project-QVHSJRUP.js +24 -0
  218. package/dist/update-prompt-GDKRYJI3.js +140 -0
  219. package/dist/update-prompt-MY263DYF.js +141 -0
  220. package/dist/update-scenario-7KA3JDXA.js +34 -0
  221. package/dist/{update-scenario-NEXCAQI7.js → update-scenario-MOHRQKAQ.js} +11 -4
  222. package/package.json +39 -44
  223. package/pyproject.toml +8 -8
  224. package/src/config.d.ts +4 -0
  225. package/src/config.ts +20 -29
  226. package/src/create-mcp-server.ts +1021 -596
  227. package/src/documentation-fetch.ts +21 -20
  228. package/src/http-security.ts +166 -135
  229. package/src/http-server.ts +189 -224
  230. package/src/index.ts +14 -16
  231. package/src/langwatch-api-agents.ts +202 -21
  232. package/src/langwatch-api-annotations.ts +11 -12
  233. package/src/langwatch-api-api-keys.ts +8 -9
  234. package/src/langwatch-api-boards.ts +46 -0
  235. package/src/langwatch-api-dashboard-widgets.ts +112 -0
  236. package/src/langwatch-api-dashboards.ts +13 -10
  237. package/src/langwatch-api-datasets.ts +98 -32
  238. package/src/langwatch-api-evaluators.ts +13 -19
  239. package/src/langwatch-api-model-providers.ts +4 -4
  240. package/src/langwatch-api-monitors.ts +14 -18
  241. package/src/langwatch-api-projects.ts +27 -9
  242. package/src/langwatch-api-query.ts +114 -0
  243. package/src/langwatch-api-run-plans.ts +140 -0
  244. package/src/langwatch-api-scenarios.ts +17 -10
  245. package/src/langwatch-api-secrets.ts +31 -24
  246. package/src/langwatch-api-simulation-runs.ts +10 -7
  247. package/src/langwatch-api-test-suites.ts +116 -0
  248. package/src/langwatch-api-triggers.ts +96 -34
  249. package/src/langwatch-api-workflows.ts +9 -9
  250. package/src/langwatch-api.ts +143 -109
  251. package/src/public-http-request.ts +21 -12
  252. package/src/schemas/analytics-metrics.ts +3 -15
  253. package/src/schemas/create-dataset.ts +3 -4
  254. package/src/schemas/run-plan.ts +76 -0
  255. package/src/schemas/suite-fields.ts +132 -0
  256. package/src/schemas/triggers.ts +365 -0
  257. package/src/tools/add-dashboard-widget.ts +63 -0
  258. package/src/tools/archive-project.ts +2 -4
  259. package/src/tools/archive-run-plan.ts +10 -0
  260. package/src/tools/archive-scenario.ts +2 -4
  261. package/src/tools/archive-test-suite.ts +10 -0
  262. package/src/tools/assign-prompt-tag.ts +6 -2
  263. package/src/tools/create-agent.ts +1 -1
  264. package/src/tools/create-annotation.ts +7 -2
  265. package/src/tools/create-api-key.ts +4 -6
  266. package/src/tools/create-dashboard.ts +1 -1
  267. package/src/tools/create-dataset-records.ts +4 -8
  268. package/src/tools/create-dataset.ts +5 -7
  269. package/src/tools/create-evaluator.ts +3 -5
  270. package/src/tools/create-project.ts +2 -2
  271. package/src/tools/create-prompt-tag.ts +2 -4
  272. package/src/tools/create-prompt.ts +9 -18
  273. package/src/tools/create-scenario.ts +11 -5
  274. package/src/tools/create-test-suite.ts +38 -0
  275. package/src/tools/create-trigger.ts +55 -0
  276. package/src/tools/dashboard-exists.ts +25 -0
  277. package/src/tools/dashboard-widget-queries.ts +32 -0
  278. package/src/tools/delete-agent.ts +1 -1
  279. package/src/tools/delete-annotation.ts +1 -1
  280. package/src/tools/delete-dashboard.ts +1 -1
  281. package/src/tools/delete-dataset-records.ts +4 -8
  282. package/src/tools/delete-dataset.ts +3 -7
  283. package/src/tools/delete-evaluator.ts +4 -6
  284. package/src/tools/delete-prompt-tag.ts +2 -4
  285. package/src/tools/delete-workflow.ts +1 -1
  286. package/src/tools/discover-evaluator-schema.ts +34 -25
  287. package/src/tools/discover-scenario-schema.ts +86 -12
  288. package/src/tools/discover-schema.ts +149 -27
  289. package/src/tools/experiment-run-status.ts +2 -8
  290. package/src/tools/format-dataset-mutation.ts +5 -10
  291. package/src/tools/format-run-plan.ts +54 -0
  292. package/src/tools/format-scenario.ts +12 -0
  293. package/src/tools/format-suite-details.ts +90 -0
  294. package/src/tools/get-agent.ts +54 -3
  295. package/src/tools/get-analytics.ts +52 -46
  296. package/src/tools/get-dashboard.ts +1 -1
  297. package/src/tools/get-dataset.ts +34 -16
  298. package/src/tools/get-evaluator.ts +4 -11
  299. package/src/tools/get-experiment-results.ts +219 -186
  300. package/src/tools/get-project.ts +2 -4
  301. package/src/tools/get-prompt.ts +100 -37
  302. package/src/tools/get-run-plan.ts +60 -0
  303. package/src/tools/get-scenario.ts +6 -5
  304. package/src/tools/get-simulation-run.ts +32 -20
  305. package/src/tools/get-test-suite.ts +46 -0
  306. package/src/tools/get-trace.ts +16 -17
  307. package/src/tools/get-workflow.ts +1 -1
  308. package/src/tools/list-agents.ts +20 -3
  309. package/src/tools/list-annotations.ts +8 -5
  310. package/src/tools/list-api-keys.ts +7 -2
  311. package/src/tools/list-dashboard-boards.ts +28 -0
  312. package/src/tools/list-dashboards.ts +1 -1
  313. package/src/tools/list-dataset-records.ts +8 -6
  314. package/src/tools/list-datasets.ts +12 -23
  315. package/src/tools/list-evaluators.ts +4 -8
  316. package/src/tools/list-experiment-runs.ts +5 -9
  317. package/src/tools/list-experiments.ts +5 -11
  318. package/src/tools/list-model-providers.ts +3 -5
  319. package/src/tools/list-projects.ts +2 -4
  320. package/src/tools/list-prompt-tags.ts +3 -3
  321. package/src/tools/list-prompts.ts +4 -8
  322. package/src/tools/list-run-plans.ts +47 -0
  323. package/src/tools/list-scenarios.ts +26 -17
  324. package/src/tools/list-simulation-runs.ts +13 -5
  325. package/src/tools/list-test-suites.ts +35 -0
  326. package/src/tools/list-trigger-fires.ts +17 -0
  327. package/src/tools/list-triggers.ts +39 -0
  328. package/src/tools/list-workflows.ts +2 -4
  329. package/src/tools/rename-prompt-tag.ts +1 -1
  330. package/src/tools/rename-test-suite.ts +15 -0
  331. package/src/tools/report-issue.ts +5 -9
  332. package/src/tools/rerun-run-plan.ts +18 -0
  333. package/src/tools/revoke-api-key.ts +2 -4
  334. package/src/tools/run-agent.ts +65 -14
  335. package/src/tools/run-experiment.ts +36 -37
  336. package/src/tools/run-plan.ts +46 -0
  337. package/src/tools/run-query.ts +47 -0
  338. package/src/tools/run-test-suite.ts +27 -0
  339. package/src/tools/run-workflow.ts +2 -5
  340. package/src/tools/search-traces.ts +161 -47
  341. package/src/tools/set-model-provider.ts +4 -6
  342. package/src/tools/test-agent.ts +18 -0
  343. package/src/tools/update-agent.ts +1 -1
  344. package/src/tools/update-dashboard-widget.ts +97 -0
  345. package/src/tools/update-dataset-record.ts +1 -1
  346. package/src/tools/update-dataset.ts +5 -7
  347. package/src/tools/update-evaluator.ts +3 -5
  348. package/src/tools/update-project.ts +1 -1
  349. package/src/tools/update-prompt.ts +185 -15
  350. package/src/tools/update-scenario.ts +11 -5
  351. package/src/tools/update-test-suite.ts +40 -0
  352. package/src/utils/date-parsing.ts +1 -1
  353. package/src/utils/escape-markdown.ts +3 -8
  354. package/src/utils/format-evaluations.ts +6 -6
  355. package/src/utils/markdown-table.ts +58 -0
  356. package/src/utils/trace-id-shape.ts +20 -0
  357. package/tsconfig.app.json +24 -0
  358. package/tsconfig.json +19 -12
  359. package/uv.lock +416 -296
  360. package/vitest.integration.config.js +10 -0
  361. package/vitest.scenario.config.js +9 -0
  362. package/.eslintrc.cjs +0 -36
  363. package/dist/archive-suite-4T6BSRS7.js +0 -16
  364. package/dist/chunk-77BNWQ3U.js +0 -86
  365. package/dist/chunk-A2LEVYQ5.js +0 -217
  366. package/dist/chunk-TJCXFNDT.js +0 -24
  367. package/dist/chunk-YZLYHLLI.js +0 -48
  368. package/dist/config.d.ts +0 -17
  369. package/dist/create-mcp-server.d.ts +0 -10
  370. package/dist/create-suite-LZS6QIAF.js +0 -39
  371. package/dist/delete-evaluator-33SP2JFX.js +0 -16
  372. package/dist/discover-evaluator-schema-XTAD4X7D.js +0 -2191
  373. package/dist/discover-scenario-schema-FE64QK2D.js +0 -67
  374. package/dist/get-agent-Y7MWZ65F.js +0 -29
  375. package/dist/get-analytics-TETCSOK6.js +0 -73
  376. package/dist/get-dataset-RCMU5VAH.js +0 -49
  377. package/dist/get-prompt-OY5B7TRI.js +0 -46
  378. package/dist/get-suite-J7JVWO6O.js +0 -45
  379. package/dist/index.d.ts +0 -2
  380. package/dist/langwatch-api-secrets-BUBSNCNO.js +0 -41
  381. package/dist/langwatch-api-triggers-EDFUCJN6.js +0 -31
  382. package/dist/list-agents-KFHKGPXV.js +0 -33
  383. package/dist/list-scenarios-F2WYNRIE.js +0 -41
  384. package/dist/list-suites-CRY4VWW6.js +0 -43
  385. package/dist/report-issue-XYVAPAYJ.js +0 -412
  386. package/dist/run-suite-NKZCJ2IN.js +0 -30
  387. package/dist/search-traces-A6HFKPLJ.js +0 -79
  388. package/dist/update-prompt-ODCRQRN6.js +0 -25
  389. package/dist/update-suite-SNO3GS7T.js +0 -40
  390. package/src/__tests__/all-tools.integration.test.ts +0 -1626
  391. package/src/__tests__/config.unit.test.ts +0 -202
  392. package/src/__tests__/datasets.integration.test.ts +0 -620
  393. package/src/__tests__/datasets.unit.test.ts +0 -244
  394. package/src/__tests__/date-parsing.unit.test.ts +0 -78
  395. package/src/__tests__/discover-evaluator-schema.unit.test.ts +0 -89
  396. package/src/__tests__/discover-schema.unit.test.ts +0 -118
  397. package/src/__tests__/docker.integration.test.ts +0 -318
  398. package/src/__tests__/documentation-fetch-security.integration.test.ts +0 -182
  399. package/src/__tests__/evaluator-tools.unit.test.ts +0 -262
  400. package/src/__tests__/experiment-list-tools.unit.test.ts +0 -157
  401. package/src/__tests__/experiment-status.unit.test.ts +0 -194
  402. package/src/__tests__/feature-map-tool-drift.unit.test.ts +0 -93
  403. package/src/__tests__/get-experiment-results.unit.test.ts +0 -313
  404. package/src/__tests__/http-security-origin.unit.test.ts +0 -97
  405. package/src/__tests__/http-security-sessions.unit.test.ts +0 -209
  406. package/src/__tests__/http-security-verifier.unit.test.ts +0 -248
  407. package/src/__tests__/http-server-allocation.integration.test.ts +0 -279
  408. package/src/__tests__/http-server-origin.integration.test.ts +0 -169
  409. package/src/__tests__/http-server-session-auth.integration.test.ts +0 -341
  410. package/src/__tests__/http-transport.integration.test.ts +0 -395
  411. package/src/__tests__/integration.integration.test.ts +0 -288
  412. package/src/__tests__/langwatch-api.unit.test.ts +0 -661
  413. package/src/__tests__/model-provider-tools.unit.test.ts +0 -190
  414. package/src/__tests__/project-api-key-tools.unit.test.ts +0 -451
  415. package/src/__tests__/public-http-request-security.integration.test.ts +0 -118
  416. package/src/__tests__/report-issue.integration.test.ts +0 -147
  417. package/src/__tests__/scenario-tools.integration.test.ts +0 -286
  418. package/src/__tests__/scenario-tools.unit.test.ts +0 -188
  419. package/src/__tests__/schemas.unit.test.ts +0 -85
  420. package/src/__tests__/support/http-server-harness.ts +0 -138
  421. package/src/__tests__/tools.unit.test.ts +0 -924
  422. package/src/langwatch-api-suites.ts +0 -118
  423. package/src/schemas/filter-fields.ts +0 -119
  424. package/src/schemas/index.ts +0 -4
  425. package/src/tools/archive-suite.ts +0 -12
  426. package/src/tools/create-suite.ts +0 -42
  427. package/src/tools/get-suite.ts +0 -47
  428. package/src/tools/list-suites.ts +0 -43
  429. package/src/tools/run-suite.ts +0 -28
  430. package/src/tools/update-suite.ts +0 -44
@@ -1,313 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
-
3
- vi.mock("../langwatch-api.js", async (importOriginal) => {
4
- const actual = (await importOriginal()) as Record<string, unknown>;
5
- return {
6
- ...actual,
7
- makeRequest: vi.fn(),
8
- };
9
- });
10
-
11
- import { LangWatchApiError, makeRequest } from "../langwatch-api.js";
12
- import { handleExperimentResults } from "../tools/get-experiment-results.js";
13
-
14
- const mockMakeRequest = vi.mocked(makeRequest);
15
-
16
- const sample = {
17
- experimentId: "exp_1",
18
- runId: "run_1",
19
- projectId: "proj_1",
20
- progress: 3,
21
- total: 3,
22
- dataset: [
23
- { index: 0, entry: { input: "hello world" } },
24
- { index: 1, entry: { input: "broken" }, error: "boom" },
25
- { index: 2, entry: { input: "low" } },
26
- ],
27
- evaluations: [
28
- { evaluator: "quality", index: 0, status: "processed", score: 0.9, passed: true },
29
- { evaluator: "quality", index: 2, status: "processed", score: 0.2, passed: false, details: "off-topic" },
30
- { evaluator: "safety", index: 0, status: "processed", score: 1.0, passed: true },
31
- ],
32
- timestamps: { createdAt: 0, updatedAt: 0, finishedAt: 1 },
33
- };
34
-
35
- beforeEach(() => {
36
- vi.clearAllMocks();
37
- });
38
-
39
- describe("handleExperimentResults()", () => {
40
- describe("given a completed run", () => {
41
- describe("when called without filters", () => {
42
- it("hits the v3 results endpoint with the run id", async () => {
43
- mockMakeRequest.mockResolvedValueOnce(sample);
44
- await handleExperimentResults({ runId: "run_1" });
45
- expect(mockMakeRequest).toHaveBeenCalledWith(
46
- "GET",
47
- "/api/experiments/runs/run_1/results",
48
- );
49
- });
50
-
51
- it("renders evaluator averages and per-row sections in markdown", async () => {
52
- mockMakeRequest.mockResolvedValueOnce(sample);
53
- const out = await handleExperimentResults({ runId: "run_1" });
54
- expect(out).toContain("# Evaluation Results: run_1");
55
- expect(out).toContain("## Evaluator Summary");
56
- expect(out).toContain("quality");
57
- expect(out).toContain("safety");
58
- expect(out).toContain("Row #0");
59
- expect(out).toContain("Row #1");
60
- expect(out).toContain("Row #2");
61
- });
62
- });
63
-
64
- describe("when filter is 'failed'", () => {
65
- it("includes only failed/errored rows", async () => {
66
- mockMakeRequest.mockResolvedValueOnce(sample);
67
- const out = await handleExperimentResults({
68
- runId: "run_1",
69
- filter: "failed",
70
- });
71
- expect(out).toContain("Row #1");
72
- expect(out).toContain("Row #2");
73
- expect(out).not.toContain("Row #0");
74
- });
75
- });
76
-
77
- describe("when an evaluator name is supplied", () => {
78
- it("limits the report to that evaluator", async () => {
79
- mockMakeRequest.mockResolvedValueOnce(sample);
80
- const out = await handleExperimentResults({
81
- runId: "run_1",
82
- evaluator: "quality",
83
- });
84
- expect(out).toContain("quality");
85
- // The per-row sections should not list the safety evaluator bullet
86
- expect(out).not.toMatch(/\*\*safety\*\*/);
87
- });
88
- });
89
-
90
- describe("when there are more rows than the cap", () => {
91
- it("truncates and notes the cap", async () => {
92
- const big = {
93
- ...sample,
94
- dataset: Array.from({ length: 80 }, (_, i) => ({
95
- index: i,
96
- entry: { input: `row ${i}` },
97
- })),
98
- evaluations: [],
99
- };
100
- mockMakeRequest.mockResolvedValueOnce(big);
101
- const out = await handleExperimentResults({ runId: "run_1" });
102
- expect(out).toContain("Output truncated to 50 rows of 80");
103
- });
104
- });
105
-
106
- describe("when a caller asks for more than the row cap", () => {
107
- it("still caps output at 50 rows", async () => {
108
- const big = {
109
- ...sample,
110
- dataset: Array.from({ length: 80 }, (_, i) => ({
111
- index: i,
112
- entry: { input: `row ${i}` },
113
- })),
114
- evaluations: [],
115
- };
116
- mockMakeRequest.mockResolvedValueOnce(big);
117
- const out = await handleExperimentResults({
118
- runId: "run_1",
119
- limit: 5000,
120
- });
121
- expect(out).toContain("Output truncated to 50 rows of 80");
122
- });
123
- });
124
-
125
- describe("when only some rows have evaluations", () => {
126
- it("explains rows with no evaluation coverage", async () => {
127
- mockMakeRequest.mockResolvedValueOnce({
128
- ...sample,
129
- dataset: [
130
- { index: 0, entry: { input: "covered" } },
131
- { index: 1, entry: { input: "uncovered" } },
132
- ],
133
- evaluations: [
134
- {
135
- evaluator: "quality",
136
- index: 0,
137
- status: "processed",
138
- score: 0.9,
139
- passed: true,
140
- },
141
- ],
142
- });
143
-
144
- const out = await handleExperimentResults({ runId: "run_1" });
145
-
146
- expect(out).toContain("uncovered");
147
- expect(out).toContain("No evaluations recorded for this row");
148
- });
149
- });
150
-
151
- describe("when multiple targets share the same dataset index", () => {
152
- it("keeps evaluator rows target-scoped", async () => {
153
- mockMakeRequest.mockResolvedValueOnce({
154
- ...sample,
155
- dataset: [
156
- { index: 0, targetId: "a", entry: { input: "target a" } },
157
- { index: 0, targetId: "b", entry: { input: "target b" } },
158
- ],
159
- evaluations: [
160
- {
161
- evaluator: "quality",
162
- index: 0,
163
- targetId: "a",
164
- status: "processed",
165
- score: 0.9,
166
- passed: true,
167
- },
168
- {
169
- evaluator: "quality",
170
- index: 0,
171
- targetId: "b",
172
- status: "processed",
173
- score: 0.1,
174
- passed: false,
175
- details: "target-b-only",
176
- },
177
- ],
178
- });
179
-
180
- const out = await handleExperimentResults({
181
- runId: "run_1",
182
- filter: "failed",
183
- });
184
-
185
- expect(out).toContain("target b");
186
- expect(out).toContain("target-b-only");
187
- expect(out).not.toContain("target a");
188
- });
189
- });
190
- });
191
-
192
- describe("given the run is missing", () => {
193
- describe("when the API throws a 404-shaped error", () => {
194
- it("returns a graceful 'not found' markdown", async () => {
195
- mockMakeRequest.mockRejectedValueOnce(
196
- new LangWatchApiError("missing", 404, "Run not found"),
197
- );
198
- const out = await handleExperimentResults({ runId: "missing" });
199
- expect(out).toContain("not found");
200
- expect(out).toContain("experimentSlug");
201
- });
202
- });
203
- });
204
-
205
- describe("given the run is still in progress", () => {
206
- describe("when results are requested", () => {
207
- it("serves the partial rows logged so far with an in-progress note", async () => {
208
- mockMakeRequest.mockResolvedValueOnce({
209
- ...sample,
210
- progress: 2,
211
- total: 3,
212
- timestamps: {
213
- createdAt: Date.now(),
214
- updatedAt: Date.now(),
215
- finishedAt: null,
216
- stoppedAt: null,
217
- },
218
- });
219
-
220
- const out = await handleExperimentResults({ runId: "unfinished" });
221
-
222
- expect(out).toContain("**Status**: running");
223
- expect(out).toContain("still in progress");
224
- expect(out).toContain("Row #0");
225
- expect(out).toContain("Progress**: 2/3");
226
- });
227
- });
228
- });
229
-
230
- describe("given the run was interrupted before finishing", () => {
231
- describe("when results are requested", () => {
232
- it("serves the partial rows with an interrupted note", async () => {
233
- mockMakeRequest.mockResolvedValueOnce({
234
- ...sample,
235
- timestamps: {
236
- createdAt: Date.now() - 60 * 60 * 1000,
237
- updatedAt: Date.now() - 30 * 60 * 1000,
238
- finishedAt: null,
239
- stoppedAt: null,
240
- },
241
- });
242
-
243
- const out = await handleExperimentResults({ runId: "interrupted" });
244
-
245
- expect(out).toContain("**Status**: interrupted");
246
- expect(out).toContain("interrupted");
247
- expect(out).toContain("Row #0");
248
- });
249
- });
250
- });
251
-
252
- describe("given a non-terminal run with zero rows", () => {
253
- describe("when the run is still running", () => {
254
- it("says the run is in progress", async () => {
255
- mockMakeRequest.mockResolvedValueOnce({
256
- ...sample,
257
- dataset: [],
258
- evaluations: [],
259
- timestamps: {
260
- createdAt: Date.now(),
261
- updatedAt: Date.now(),
262
- finishedAt: null,
263
- stoppedAt: null,
264
- },
265
- });
266
- const out = await handleExperimentResults({ runId: "empty-running" });
267
- expect(out).toContain("still in progress");
268
- expect(out).not.toContain("interrupted");
269
- });
270
- });
271
-
272
- describe("when the run was interrupted", () => {
273
- it("says no rows were recorded before it was interrupted, not to wait", async () => {
274
- mockMakeRequest.mockResolvedValueOnce({
275
- ...sample,
276
- dataset: [],
277
- evaluations: [],
278
- timestamps: {
279
- createdAt: Date.now() - 60 * 60 * 1000,
280
- updatedAt: Date.now() - 30 * 60 * 1000,
281
- finishedAt: null,
282
- stoppedAt: null,
283
- },
284
- });
285
- const out = await handleExperimentResults({ runId: "empty-interrupted" });
286
- expect(out).toContain("interrupted");
287
- expect(out).not.toContain("still in progress");
288
- expect(out).not.toContain("Call again shortly");
289
- });
290
- });
291
- });
292
-
293
- describe("given no run state but rows exist (SDK / >24h run)", () => {
294
- describe("when results are requested with a stopped run", () => {
295
- it("renders results for a stopped run", async () => {
296
- mockMakeRequest.mockResolvedValueOnce({
297
- ...sample,
298
- timestamps: {
299
- createdAt: 0,
300
- updatedAt: 10,
301
- finishedAt: null,
302
- stoppedAt: 20,
303
- },
304
- });
305
-
306
- const out = await handleExperimentResults({ runId: "stopped" });
307
-
308
- expect(out).toContain("**Status**: stopped");
309
- expect(out).toContain("Row #0");
310
- });
311
- });
312
- });
313
- });
@@ -1,97 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import {
4
- DEFAULT_BIND_HOST,
5
- isLoopbackHost,
6
- isOriginAllowed,
7
- parseAllowedOrigins,
8
- } from "../http-security.js";
9
-
10
- describe("bind host", () => {
11
- it("defaults to loopback", () => {
12
- expect(DEFAULT_BIND_HOST).toBe("127.0.0.1");
13
- expect(isLoopbackHost(DEFAULT_BIND_HOST)).toBe(true);
14
- });
15
-
16
- it.each(["127.0.0.1", "localhost", "::1", "[::1]", " LOCALHOST "])(
17
- "recognises %s as loopback",
18
- (host) => {
19
- expect(isLoopbackHost(host)).toBe(true);
20
- }
21
- );
22
-
23
- it.each(["0.0.0.0", "::", "192.168.1.10", "10.0.0.1", "example.com"])(
24
- "does not treat %s as loopback",
25
- (host) => {
26
- expect(isLoopbackHost(host)).toBe(false);
27
- }
28
- );
29
- });
30
-
31
- describe("parseAllowedOrigins", () => {
32
- it("returns an empty list when nothing is configured", () => {
33
- expect(parseAllowedOrigins(undefined)).toEqual([]);
34
- expect(parseAllowedOrigins("")).toEqual([]);
35
- });
36
-
37
- it("splits, trims, and normalizes entries", () => {
38
- expect(
39
- parseAllowedOrigins(" https://Example.com/ , https://app.test:8443 ")
40
- ).toEqual(["https://example.com", "https://app.test:8443"]);
41
- });
42
-
43
- it("drops entries that are not usable origins", () => {
44
- expect(parseAllowedOrigins("not-an-origin, https://ok.test")).toEqual([
45
- "https://ok.test",
46
- ]);
47
- });
48
-
49
- it("deduplicates equivalent origins", () => {
50
- expect(parseAllowedOrigins("https://a.test,https://a.test/")).toEqual([
51
- "https://a.test",
52
- ]);
53
- });
54
- });
55
-
56
- describe("isOriginAllowed", () => {
57
- it.each([
58
- "http://localhost:5173",
59
- "http://127.0.0.1:3000",
60
- "http://[::1]:3000",
61
- "https://localhost",
62
- ])("always allows the loopback origin %s", (origin) => {
63
- expect(isOriginAllowed({ origin, allowedOrigins: [] })).toBe(true);
64
- });
65
-
66
- it("rejects an unlisted remote origin", () => {
67
- expect(isOriginAllowed({ origin: "https://evil.test", allowedOrigins: [] })).toBe(false);
68
- });
69
-
70
- it("allows a configured remote origin", () => {
71
- expect(isOriginAllowed({ origin: "https://app.test", allowedOrigins: ["https://app.test"] })).toBe(
72
- true
73
- );
74
- });
75
-
76
- it("matches on scheme, host and port together", () => {
77
- const allowlist = ["https://app.test"];
78
- expect(isOriginAllowed({ origin: "http://app.test", allowedOrigins: allowlist })).toBe(false);
79
- expect(isOriginAllowed({ origin: "https://app.test:8443", allowedOrigins: allowlist })).toBe(false);
80
- expect(isOriginAllowed({ origin: "https://other.test", allowedOrigins: allowlist })).toBe(false);
81
- });
82
-
83
- it("does not let a hostname that merely ends in localhost through", () => {
84
- expect(isOriginAllowed({ origin: "https://notlocalhost", allowedOrigins: [] })).toBe(false);
85
- expect(isOriginAllowed({ origin: "https://evil-localhost.test", allowedOrigins: [] })).toBe(false);
86
- });
87
-
88
- it("rejects the opaque null origin", () => {
89
- expect(isOriginAllowed({ origin: "null", allowedOrigins: [] })).toBe(false);
90
- });
91
-
92
- it("treats a rebound attacker hostname as unlisted", () => {
93
- // DNS rebinding points an attacker hostname at loopback, but the browser
94
- // still sends the attacker hostname as the origin.
95
- expect(isOriginAllowed({ origin: "http://rebind.attacker.test", allowedOrigins: [] })).toBe(false);
96
- });
97
- });
@@ -1,209 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
-
3
- import { admitOAuthToken, createSessionStore } from "../http-security.js";
4
-
5
- describe("createSessionStore", () => {
6
- function makeStore(maxAgeMs = 60_000) {
7
- const closed: string[] = [];
8
- const store = createSessionStore<{ id: string }>({
9
- maxAgeMs,
10
- closeTransport: (transport) => closed.push(transport.id),
11
- });
12
- return { store, closed };
13
- }
14
-
15
- it("stores and returns sessions bound to their key", () => {
16
- const { store } = makeStore();
17
- store.add({ sessionId: "s1", transport: { id: "s1" }, apiKey: "sk-a" });
18
-
19
- expect(store.get("s1")?.apiKey).toBe("sk-a");
20
- expect(store.size).toBe(1);
21
- });
22
-
23
- it("counts live sessions per key without holding the raw key", () => {
24
- const { store } = makeStore();
25
- store.add({ sessionId: "s1", transport: { id: "s1" }, apiKey: "sk-a" });
26
- store.add({ sessionId: "s2", transport: { id: "s2" }, apiKey: "sk-a" });
27
- store.add({ sessionId: "s3", transport: { id: "s3" }, apiKey: "sk-b" });
28
-
29
- expect(store.countForKey("sk-a")).toBe(2);
30
- expect(store.countForKey("sk-b")).toBe(1);
31
- expect(store.countForKey("sk-unknown")).toBe(0);
32
- });
33
-
34
- it("decrements the per-key count when a session is removed", () => {
35
- const { store } = makeStore();
36
- store.add({ sessionId: "s1", transport: { id: "s1" }, apiKey: "sk-a" });
37
- store.add({ sessionId: "s2", transport: { id: "s2" }, apiKey: "sk-a" });
38
-
39
- store.remove("s1");
40
- expect(store.countForKey("sk-a")).toBe(1);
41
-
42
- store.remove("s2");
43
- expect(store.countForKey("sk-a")).toBe(0);
44
- });
45
-
46
- it("ignores removal of a session it does not hold", () => {
47
- const { store } = makeStore();
48
- store.add({ sessionId: "s1", transport: { id: "s1" }, apiKey: "sk-a" });
49
-
50
- store.remove("never-existed");
51
- store.remove("never-existed");
52
-
53
- expect(store.countForKey("sk-a")).toBe(1);
54
- });
55
-
56
- it("closes and forgets sessions that have gone idle", () => {
57
- vi.useFakeTimers();
58
- try {
59
- const { store, closed } = makeStore(1_000);
60
- store.add({ sessionId: "idle", transport: { id: "idle" }, apiKey: "sk-a" });
61
- store.add({ sessionId: "busy", transport: { id: "busy" }, apiKey: "sk-a" });
62
-
63
- vi.advanceTimersByTime(900);
64
- store.touch("busy");
65
- vi.advanceTimersByTime(900);
66
- store.sweep();
67
-
68
- expect(closed).toEqual(["idle"]);
69
- expect(store.get("idle")).toBeUndefined();
70
- expect(store.get("busy")).toBeDefined();
71
- expect(store.countForKey("sk-a")).toBe(1);
72
- } finally {
73
- vi.useRealTimers();
74
- }
75
- });
76
-
77
- it("closes everything on shutdown", () => {
78
- const { store, closed } = makeStore();
79
- store.add({ sessionId: "s1", transport: { id: "s1" }, apiKey: "sk-a" });
80
- store.add({ sessionId: "s2", transport: { id: "s2" }, apiKey: "sk-b" });
81
-
82
- store.closeAll();
83
-
84
- expect(closed.sort()).toEqual(["s1", "s2"]);
85
- expect(store.size).toBe(0);
86
- expect(store.countForKey("sk-a")).toBe(0);
87
- });
88
-
89
- it("does not let a prototype-shaped session id reach the prototype chain", () => {
90
- const { store } = makeStore();
91
-
92
- expect(store.get("__proto__")).toBeUndefined();
93
- expect(store.get("constructor")).toBeUndefined();
94
-
95
- store.add({ sessionId: "__proto__", transport: { id: "weird" }, apiKey: "sk-a" });
96
- expect(store.get("__proto__")?.transport.id).toBe("weird");
97
- expect(store.size).toBe(1);
98
- });
99
- });
100
-
101
- describe("session reservations", () => {
102
- function makeStore() {
103
- return createSessionStore<{ id: string }>({
104
- maxAgeMs: 60_000,
105
- closeTransport: () => undefined,
106
- });
107
- }
108
-
109
- it("counts a reserved slot before any session exists", () => {
110
- const store = makeStore();
111
- store.reserve("sk-a");
112
-
113
- // The point of the reservation: the cap sees the slot while the session is
114
- // still being set up.
115
- expect(store.countForKey("sk-a")).toBe(1);
116
- expect(store.size).toBe(0);
117
- });
118
-
119
- it("hands the reserved slot to the session rather than counting twice", () => {
120
- const store = makeStore();
121
- store.reserve("sk-a").commit({ sessionId: "s1", transport: { id: "s1" } });
122
-
123
- expect(store.countForKey("sk-a")).toBe(1);
124
- expect(store.get("s1")?.apiKey).toBe("sk-a");
125
- });
126
-
127
- it("returns the slot when the session never came up", () => {
128
- const store = makeStore();
129
- const reservation = store.reserve("sk-a");
130
- reservation.release();
131
-
132
- expect(store.countForKey("sk-a")).toBe(0);
133
- });
134
-
135
- it("ignores a release once the slot was committed", () => {
136
- const store = makeStore();
137
- const reservation = store.reserve("sk-a");
138
- reservation.commit({ sessionId: "s1", transport: { id: "s1" } });
139
- reservation.release();
140
-
141
- expect(store.countForKey("sk-a")).toBe(1);
142
- expect(store.get("s1")).toBeDefined();
143
- });
144
-
145
- it("frees the slot when the committed session is removed", () => {
146
- const store = makeStore();
147
- store.reserve("sk-a").commit({ sessionId: "s1", transport: { id: "s1" } });
148
- store.remove("s1");
149
-
150
- expect(store.countForKey("sk-a")).toBe(0);
151
- });
152
- });
153
-
154
- describe("admitOAuthToken", () => {
155
- function entry(apiKey: string, expiresAt: number) {
156
- return { apiKey, expiresAt };
157
- }
158
-
159
- it("evicts the oldest token for a key that is at the cap", () => {
160
- const tokens = new Map([
161
- ["t1", entry("sk-a", Date.now() + 1_000)],
162
- ["t2", entry("sk-a", Date.now() + 2_000)],
163
- ["t3", entry("sk-a", Date.now() + 3_000)],
164
- ]);
165
-
166
- admitOAuthToken({ apiKey: "sk-a", tokens, maxPerKey: 3 });
167
-
168
- // Room for the caller's new token, and the longest-standing one went.
169
- expect(tokens.has("t1")).toBe(false);
170
- expect([...tokens.keys()]).toEqual(["t2", "t3"]);
171
- });
172
-
173
- it("leaves other keys alone", () => {
174
- const tokens = new Map([
175
- ["t1", entry("sk-a", Date.now() + 1_000)],
176
- ["t2", entry("sk-b", Date.now() + 1_000)],
177
- ["t3", entry("sk-b", Date.now() + 2_000)],
178
- ]);
179
-
180
- admitOAuthToken({ apiKey: "sk-a", tokens, maxPerKey: 1 });
181
-
182
- expect(tokens.has("t1")).toBe(false);
183
- expect(tokens.has("t2")).toBe(true);
184
- expect(tokens.has("t3")).toBe(true);
185
- });
186
-
187
- it("keeps a key from growing the map without bound", () => {
188
- const tokens = new Map<string, { apiKey: string; expiresAt: number }>();
189
-
190
- for (let i = 0; i < 50; i += 1) {
191
- admitOAuthToken({ apiKey: "sk-a", tokens, maxPerKey: 10 });
192
- tokens.set(`t${i}`, entry("sk-a", Date.now() + 60_000 + i));
193
- }
194
-
195
- expect(tokens.size).toBe(10);
196
- });
197
-
198
- it("drops expired tokens for any key on the way past", () => {
199
- const tokens = new Map([
200
- ["stale", entry("sk-b", Date.now() - 1)],
201
- ["live", entry("sk-b", Date.now() + 60_000)],
202
- ]);
203
-
204
- admitOAuthToken({ apiKey: "sk-a", tokens, maxPerKey: 10 });
205
-
206
- expect(tokens.has("stale")).toBe(false);
207
- expect(tokens.has("live")).toBe(true);
208
- });
209
- });