@langwatch/mcp-server 2.1.0 → 3.0.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 (428) hide show
  1. package/.gitignore +7 -0
  2. package/.npmignore +41 -0
  3. package/CHANGELOG.md +218 -201
  4. package/CONTRIBUTING.md +10 -0
  5. package/README.md +70 -68
  6. package/dist/add-dashboard-widget-55UI6H22.js +44 -0
  7. package/dist/add-dashboard-widget-MJHIC3CC.js +45 -0
  8. package/dist/{archive-project-W3IKI7UL.js → archive-project-K5UD7A6X.js} +4 -4
  9. package/dist/archive-project-L4PAKT34.js +19 -0
  10. package/dist/{archive-run-plan-474OKHHU.js → archive-run-plan-5HSQG2HX.js} +4 -4
  11. package/dist/archive-run-plan-AOKNYQIO.js +15 -0
  12. package/dist/archive-scenario-2CWJW2VD.js +19 -0
  13. package/dist/{archive-scenario-V5UNYK26.js → archive-scenario-5IJQESHH.js} +4 -4
  14. package/dist/{archive-test-suite-O7FLDMXK.js → archive-test-suite-OPB7NFI7.js} +4 -4
  15. package/dist/archive-test-suite-ZC3TN6VH.js +15 -0
  16. package/dist/assign-prompt-tag-6DDE6ZZ5.js +23 -0
  17. package/dist/{assign-prompt-tag-Q46WRB6P.js → assign-prompt-tag-D7O23MJM.js} +8 -4
  18. package/dist/chunk-3YLN3ATJ.js +241 -0
  19. package/dist/{chunk-EYXMLE5X.js → chunk-4RX3G62A.js} +56 -47
  20. package/dist/{chunk-UF6XIGYR.js → chunk-6YZUOD34.js} +2 -5
  21. package/dist/{chunk-WVI5CJ6S.js → chunk-7AQLPVFL.js} +2 -2
  22. package/dist/chunk-7CZQDZ2K.js +319 -0
  23. package/dist/{chunk-RVAE2MEA.js → chunk-7GETGE6B.js} +7 -7
  24. package/dist/{chunk-OJLYYJWI.js → chunk-AASW2TO2.js} +7 -10
  25. package/dist/{chunk-QAMP3CGR.js → chunk-B4RFWIUP.js} +4 -4
  26. package/dist/{chunk-ZXKLPC2E.js → chunk-BIIKE3EI.js} +1 -1
  27. package/dist/{chunk-G4ZAZTZW.js → chunk-D2HS73VI.js} +7 -10
  28. package/dist/{chunk-37SMPMQS.js → chunk-DMM3JQ42.js} +7 -2
  29. package/dist/{chunk-WT4PO4A3.js → chunk-DULFP3MK.js} +10 -7
  30. package/dist/chunk-EYPJBL7P.js +516 -0
  31. package/dist/{chunk-NUFPTHKU.js → chunk-FOKVY3BK.js} +1 -1
  32. package/dist/chunk-GA5JUSPT.js +64 -0
  33. package/dist/{chunk-UJPXFA26.js → chunk-HHMNZAPD.js} +101 -74
  34. package/dist/chunk-HLQJXUR6.js +67 -0
  35. package/dist/{chunk-JTQJRNFJ.js → chunk-KILVK3FS.js} +4 -4
  36. package/dist/chunk-MOCU5XWF.js +79 -0
  37. package/dist/chunk-O2WGVCED.js +41 -0
  38. package/dist/{chunk-HV4DWKBI.js → chunk-O7EMWEVJ.js} +2 -4
  39. package/dist/{chunk-UFOYFYI7.js → chunk-QXLMHLRC.js} +6 -9
  40. package/dist/{chunk-QRO35SOM.js → chunk-R5YJIYPF.js} +981 -747
  41. package/dist/{chunk-JR6DJZOU.js → chunk-RABYBFX5.js} +4 -10
  42. package/dist/{chunk-SR6QPRIE.js → chunk-RFC2SBEX.js} +7 -13
  43. package/dist/chunk-SHSVN3SE.js +21 -0
  44. package/dist/{chunk-CKNAH5TG.js → chunk-U24EACC4.js} +3 -10
  45. package/dist/{chunk-56OGTS24.js → chunk-VOUGDY2O.js} +4 -11
  46. package/dist/chunk-WCKZDOGF.js +31 -0
  47. package/dist/chunk-XQYH52SI.js +129 -0
  48. package/dist/{chunk-7E4FZIYJ.js → chunk-XUIJYNRJ.js} +1 -1
  49. package/dist/{chunk-6DJDEXET.js → chunk-YCIUEZVK.js} +6 -12
  50. package/dist/config-2WQGMAEN.js +16 -0
  51. package/dist/config.cjs +109 -0
  52. package/dist/config.js +5 -4
  53. package/dist/{create-agent-JFJ2K6X3.js → create-agent-6JNLMYQD.js} +4 -4
  54. package/dist/create-agent-IRZOU4R5.js +23 -0
  55. package/dist/create-analytics-report-N4L4V5QL.js +19 -0
  56. package/dist/create-analytics-report-QEHXG7FU.js +18 -0
  57. package/dist/{create-annotation-EUXXQ6NS.js → create-annotation-CCEGIZ67.js} +10 -5
  58. package/dist/create-annotation-DTHCMWHB.js +29 -0
  59. package/dist/{create-api-key-BGBB76JX.js → create-api-key-JKM2DSZO.js} +5 -7
  60. package/dist/create-api-key-JPEUIXGM.js +24 -0
  61. package/dist/create-dataset-DJNXYJ5B.js +22 -0
  62. package/dist/{create-dataset-FD5VAXU4.js → create-dataset-WON4SOTC.js} +6 -6
  63. package/dist/create-dataset-records-TMEOTLTO.js +20 -0
  64. package/dist/{create-dataset-records-WQUXXOHI.js → create-dataset-records-UJBW3C7C.js} +5 -7
  65. package/dist/create-evaluator-N5AEHI4K.js +27 -0
  66. package/dist/{create-evaluator-6NV33Z52.js → create-evaluator-XYNY2YFY.js} +4 -4
  67. package/dist/create-mcp-server.cjs +9712 -0
  68. package/dist/create-mcp-server.js +2757 -10
  69. package/dist/{create-project-ZQ7TFIO2.js → create-project-EY4XZ7TH.js} +4 -4
  70. package/dist/create-project-YL345T5L.js +44 -0
  71. package/dist/{create-prompt-BS6QRCUU.js → create-prompt-JZQNQ7I3.js} +5 -7
  72. package/dist/create-prompt-ZUMQHHX7.js +37 -0
  73. package/dist/create-prompt-tag-CJ5FZ436.js +17 -0
  74. package/dist/{create-prompt-tag-FCQ5ELSQ.js → create-prompt-tag-JFP2Q3PO.js} +3 -3
  75. package/dist/create-scenario-CQU2SSFB.js +33 -0
  76. package/dist/{create-scenario-DRPTPXU3.js → create-scenario-S6OWWKJO.js} +5 -5
  77. package/dist/create-test-suite-OLGCAGJA.js +36 -0
  78. package/dist/{create-test-suite-ZIZCCFJ4.js → create-test-suite-Y7P2RI4L.js} +6 -6
  79. package/dist/create-trigger-4CNBTISJ.js +48 -0
  80. package/dist/create-trigger-RQ653WYF.js +49 -0
  81. package/dist/dashboards-BA7M6CMO.js +90 -0
  82. package/dist/dashboards-U663GUIB.js +91 -0
  83. package/dist/{delete-agent-SA4JMUZF.js → delete-agent-J2Q4DRZ2.js} +4 -4
  84. package/dist/delete-agent-ZAU4LTXB.js +18 -0
  85. package/dist/delete-analytics-report-QK6X7P4S.js +19 -0
  86. package/dist/delete-analytics-report-YUYLDHLL.js +18 -0
  87. package/dist/delete-annotation-7X4NYXXM.js +17 -0
  88. package/dist/{delete-annotation-X3KKK7QY.js → delete-annotation-KJOCDLV3.js} +4 -4
  89. package/dist/{delete-dataset-W6EZ44HY.js → delete-dataset-NXP7ZPPV.js} +5 -7
  90. package/dist/delete-dataset-RUTZBBVD.js +18 -0
  91. package/dist/{delete-dataset-records-7CNOTALB.js → delete-dataset-records-4Z7DZCDW.js} +5 -7
  92. package/dist/delete-dataset-records-75UAR5IS.js +19 -0
  93. package/dist/delete-evaluator-E3CUNGTQ.js +16 -0
  94. package/dist/delete-evaluator-RI4GALGX.js +15 -0
  95. package/dist/delete-prompt-tag-LE3WJPKS.js +17 -0
  96. package/dist/{delete-prompt-tag-QB3OCZUV.js → delete-prompt-tag-S4KBZTNG.js} +3 -3
  97. package/dist/delete-workflow-4CZFIQCQ.js +17 -0
  98. package/dist/{delete-workflow-NG2VXYYM.js → delete-workflow-PWQPDW6H.js} +4 -4
  99. package/dist/discover-evaluator-schema-UHWA5HVL.js +120 -0
  100. package/dist/discover-evaluator-schema-XKCLLCB3.js +118 -0
  101. package/dist/discover-scenario-schema-NOJY6S7E.js +137 -0
  102. package/dist/{discover-scenario-schema-W7KBXA4J.js → discover-scenario-schema-OOFIQKOG.js} +37 -17
  103. package/dist/discover-schema-7WHIIATI.js +442 -0
  104. package/dist/{discover-schema-L6QDPON3.js → discover-schema-J2ANOBKO.js} +181 -186
  105. package/dist/get-agent-5T3CXQOR.js +69 -0
  106. package/dist/{get-agent-7THF7HCU.js → get-agent-ZKYQJUQ7.js} +15 -11
  107. package/dist/get-analytics-4MPEK4KV.js +79 -0
  108. package/dist/get-analytics-ZLI3A4F5.js +80 -0
  109. package/dist/get-analytics-report-JCKYMA2S.js +29 -0
  110. package/dist/get-analytics-report-SKIP2JWH.js +30 -0
  111. package/dist/get-dataset-APGZBYMH.js +64 -0
  112. package/dist/get-dataset-LWXDVMVE.js +63 -0
  113. package/dist/get-evaluator-3A7BJBAB.js +47 -0
  114. package/dist/{get-evaluator-RZ44UIN6.js → get-evaluator-IWNJINRY.js} +4 -4
  115. package/dist/get-project-EQVB4LEL.js +26 -0
  116. package/dist/{get-project-QSQEAB33.js → get-project-RK6AJTEP.js} +4 -4
  117. package/dist/{get-prompt-GVKXF5NL.js → get-prompt-TLG2T2DS.js} +53 -58
  118. package/dist/get-prompt-ZER557X3.js +94 -0
  119. package/dist/get-run-plan-5X7DDJRN.js +58 -0
  120. package/dist/{get-run-plan-NY7J5GRE.js → get-run-plan-PEHRO5M3.js} +7 -9
  121. package/dist/{get-scenario-Q3LLQE4T.js → get-scenario-375W5D6I.js} +5 -5
  122. package/dist/get-scenario-UQX45ZZO.js +37 -0
  123. package/dist/{get-simulation-run-WINEYDMK.js → get-simulation-run-2NBXJADG.js} +26 -22
  124. package/dist/get-simulation-run-QZTLGL7W.js +69 -0
  125. package/dist/{get-test-suite-4HKA7OKS.js → get-test-suite-GVIBDG7K.js} +5 -5
  126. package/dist/get-test-suite-OODIR3RI.js +48 -0
  127. package/dist/{get-trace-RCUV7N4O.js → get-trace-AGCHVPJK.js} +11 -13
  128. package/dist/get-trace-KJLYAZRC.js +45 -0
  129. package/dist/get-workflow-3TV2YYC5.js +25 -0
  130. package/dist/{get-workflow-EEOC7Y45.js → get-workflow-7LZSVPP2.js} +4 -4
  131. package/dist/{http-security-EU3CHTY7.js → http-security-DNLBNQZL.js} +2 -2
  132. package/dist/{http-server-WCDUARTH.js → http-server-7CCN3EHJ.js} +4249 -325
  133. package/dist/index.js +59595 -38027
  134. package/dist/langwatch-api-analytics-reports-6CF6KUET.js +17 -0
  135. package/dist/langwatch-api-analytics-reports-RPFB5AO5.js +18 -0
  136. package/dist/{langwatch-api-annotations-6DHVZWUK.js → langwatch-api-annotations-H4AI4TKG.js} +4 -4
  137. package/dist/langwatch-api-annotations-QSJNUQQS.js +17 -0
  138. package/dist/langwatch-api-monitors-ENHLOL3S.js +47 -0
  139. package/dist/{langwatch-api-monitors-6ISFJ7WX.js → langwatch-api-monitors-OHSGADTH.js} +11 -16
  140. package/dist/langwatch-api-secrets-AGW6UUMD.js +55 -0
  141. package/dist/langwatch-api-secrets-CLTS766Q.js +56 -0
  142. package/dist/langwatch-api-triggers-G5UWFNN2.js +22 -0
  143. package/dist/langwatch-api-triggers-QMR7KEXW.js +23 -0
  144. package/dist/langwatch-api-workflows-HKTBIKKY.js +15 -0
  145. package/dist/{langwatch-api-workflows-6UJL5U4Z.js → langwatch-api-workflows-OMROZH2O.js} +4 -4
  146. package/dist/{list-agents-AMVDQPQL.js → list-agents-BYWDNAHT.js} +4 -4
  147. package/dist/list-agents-MG24MHZE.js +42 -0
  148. package/dist/list-analytics-reports-G3EPWGSQ.js +32 -0
  149. package/dist/list-analytics-reports-PPWIFRES.js +33 -0
  150. package/dist/list-annotations-QCLGEK5A.js +38 -0
  151. package/dist/{list-annotations-6PR5NLIC.js → list-annotations-Y4EUBJHG.js} +10 -5
  152. package/dist/list-api-keys-DAVOBPFK.js +42 -0
  153. package/dist/{list-api-keys-GVPNBIHQ.js → list-api-keys-JMW5WRXX.js} +10 -5
  154. package/dist/list-dashboards-2P3VJFRS.js +29 -0
  155. package/dist/list-dashboards-MW5O3MKH.js +30 -0
  156. package/dist/list-dataset-records-KEHT7G6Q.js +43 -0
  157. package/dist/{list-dataset-records-O67HZ35Y.js → list-dataset-records-KMLLMZAZ.js} +14 -10
  158. package/dist/{list-datasets-WXZL6UMP.js → list-datasets-P3JK2ZHS.js} +9 -13
  159. package/dist/list-datasets-Z7OOE4KI.js +56 -0
  160. package/dist/{list-evaluators-PAX72WCN.js → list-evaluators-7RRESBOU.js} +5 -7
  161. package/dist/list-evaluators-VATJVOHR.js +32 -0
  162. package/dist/{list-model-providers-VVLZMLCX.js → list-model-providers-OXDRPUCJ.js} +4 -4
  163. package/dist/list-model-providers-VXQJWDXU.js +35 -0
  164. package/dist/{list-projects-AQVI6SUK.js → list-projects-HUVK7L3W.js} +5 -7
  165. package/dist/list-projects-X72ZXVY5.js +40 -0
  166. package/dist/{list-prompt-tags-WZFBJOHG.js → list-prompt-tags-3I3LWFIR.js} +3 -3
  167. package/dist/list-prompt-tags-DRHUH2ND.js +23 -0
  168. package/dist/list-prompts-6DX3QECJ.js +30 -0
  169. package/dist/{list-prompts-BQOHRHYT.js → list-prompts-PS423RU3.js} +4 -6
  170. package/dist/list-run-plans-5SXPOV3X.js +45 -0
  171. package/dist/{list-run-plans-UBAU2DGH.js → list-run-plans-6VNHINPY.js} +5 -5
  172. package/dist/list-scenarios-EQWCW7Q4.js +45 -0
  173. package/dist/{list-scenarios-MS4IUIVM.js → list-scenarios-GKNH3YND.js} +13 -11
  174. package/dist/{list-simulation-runs-D6YF4ZOU.js → list-simulation-runs-KHY7KALI.js} +12 -8
  175. package/dist/list-simulation-runs-PUDK32C2.js +53 -0
  176. package/dist/list-test-suites-7H2DRILK.js +34 -0
  177. package/dist/{list-test-suites-L7R2O6DR.js → list-test-suites-GY56LGLY.js} +4 -4
  178. package/dist/list-trigger-fires-7RZUP54Y.js +19 -0
  179. package/dist/list-trigger-fires-OZUF65LI.js +20 -0
  180. package/dist/list-triggers-BGUCUXTJ.js +45 -0
  181. package/dist/list-triggers-XC5KMX4C.js +46 -0
  182. package/dist/list-workflows-55NYC3PC.js +33 -0
  183. package/dist/{list-workflows-6MV4XJHF.js → list-workflows-HV7FUWZA.js} +5 -7
  184. package/dist/{rename-prompt-tag-K5LQ4CY6.js → rename-prompt-tag-2I7Z5FCY.js} +3 -3
  185. package/dist/rename-prompt-tag-SZYTOWDX.js +18 -0
  186. package/dist/{rename-test-suite-NTYHAO5X.js → rename-test-suite-27AYY33K.js} +4 -4
  187. package/dist/rename-test-suite-CLIEPZDO.js +20 -0
  188. package/dist/report-issue-5HSL7CNB.js +924 -0
  189. package/dist/{report-issue-RHZZ33IS.js → report-issue-N7ND5TM6.js} +107 -67
  190. package/dist/{rerun-run-plan-MUDWJBE4.js → rerun-run-plan-O3VUZ6CG.js} +5 -5
  191. package/dist/rerun-run-plan-OJTVVGJI.js +21 -0
  192. package/dist/{revoke-api-key-6LAZ3QZZ.js → revoke-api-key-BU6BKGCG.js} +4 -4
  193. package/dist/revoke-api-key-NZNCFOIR.js +15 -0
  194. package/dist/run-agent-244O4Y5M.js +75 -0
  195. package/dist/run-agent-HPCGZGYW.js +74 -0
  196. package/dist/run-plan-2ZPGGZ4H.js +38 -0
  197. package/dist/{run-plan-T4ZYOZGA.js → run-plan-KSXUYQT3.js} +7 -7
  198. package/dist/run-query-4LMOQ5UW.js +47 -0
  199. package/dist/run-query-BQUHIUC2.js +46 -0
  200. package/dist/{run-test-suite-L4MZ7G4D.js → run-test-suite-P3WLVPXC.js} +6 -6
  201. package/dist/run-test-suite-UEOMCON6.js +25 -0
  202. package/dist/run-workflow-EFZ6HG5B.js +36 -0
  203. package/dist/{run-workflow-DFIS23AP.js → run-workflow-I4IHEN54.js} +4 -4
  204. package/dist/search-traces-6IR7PR4L.js +170 -0
  205. package/dist/search-traces-BYML2NQ4.js +171 -0
  206. package/dist/set-model-provider-RBNGWFVI.js +33 -0
  207. package/dist/{set-model-provider-XRNMFOVR.js → set-model-provider-SHEVKWRC.js} +4 -4
  208. package/dist/{update-agent-DUWNYBNA.js → update-agent-PTKTM2B5.js} +4 -4
  209. package/dist/update-agent-ZPXWRMS2.js +20 -0
  210. package/dist/update-dashboard-widget-HKCBXH7X.js +62 -0
  211. package/dist/update-dashboard-widget-IP2TTQ5G.js +63 -0
  212. package/dist/update-dataset-GHIN6JR3.js +22 -0
  213. package/dist/{update-dataset-YSOMDCOB.js → update-dataset-IRVMY5PF.js} +6 -6
  214. package/dist/update-dataset-record-6VI7K2CL.js +19 -0
  215. package/dist/{update-dataset-record-DEA2ITYR.js → update-dataset-record-Z3L7ACNF.js} +4 -4
  216. package/dist/update-evaluator-5NLNE6VY.js +24 -0
  217. package/dist/{update-evaluator-NH7Q5GWV.js → update-evaluator-FEH7H6NS.js} +4 -4
  218. package/dist/update-project-75H3LDQT.js +24 -0
  219. package/dist/{update-project-Q26FBSYH.js → update-project-QPR3QX55.js} +4 -4
  220. package/dist/update-prompt-BCDFTNLK.js +140 -0
  221. package/dist/{update-prompt-ZQQ2C3KU.js → update-prompt-NPRXYSWA.js} +27 -29
  222. package/dist/update-scenario-BNOMDCMV.js +34 -0
  223. package/dist/{update-scenario-WUX34JAR.js → update-scenario-EM5XGMB7.js} +5 -5
  224. package/package.json +35 -39
  225. package/pyproject.toml +23 -8
  226. package/src/config.d.ts +6 -0
  227. package/src/config.ts +22 -29
  228. package/src/create-mcp-server.ts +887 -634
  229. package/src/documentation-fetch.ts +21 -20
  230. package/src/http-security.ts +166 -135
  231. package/src/http-server.ts +189 -224
  232. package/src/index.ts +14 -16
  233. package/src/langwatch-api-agents.ts +18 -21
  234. package/src/langwatch-api-analytics-reports.ts +62 -0
  235. package/src/langwatch-api-annotations.ts +11 -12
  236. package/src/langwatch-api-api-keys.ts +8 -9
  237. package/src/langwatch-api-dashboard-widgets.ts +47 -0
  238. package/src/langwatch-api-dashboards.ts +109 -24
  239. package/src/langwatch-api-datasets.ts +98 -32
  240. package/src/langwatch-api-evaluators.ts +13 -19
  241. package/src/langwatch-api-model-providers.ts +4 -4
  242. package/src/langwatch-api-monitors.ts +14 -18
  243. package/src/langwatch-api-projects.ts +10 -9
  244. package/src/langwatch-api-query.ts +114 -0
  245. package/src/langwatch-api-run-plans.ts +12 -29
  246. package/src/langwatch-api-scenarios.ts +9 -11
  247. package/src/langwatch-api-secrets.ts +31 -24
  248. package/src/langwatch-api-simulation-runs.ts +6 -8
  249. package/src/langwatch-api-test-suites.ts +14 -33
  250. package/src/langwatch-api-triggers.ts +96 -34
  251. package/src/langwatch-api-workflows.ts +9 -9
  252. package/src/langwatch-api.ts +118 -124
  253. package/src/public-http-request.ts +21 -12
  254. package/src/schemas/analytics-metrics.ts +3 -15
  255. package/src/schemas/create-dataset.ts +3 -4
  256. package/src/schemas/run-plan.ts +13 -29
  257. package/src/schemas/suite-fields.ts +6 -23
  258. package/src/schemas/triggers.ts +365 -0
  259. package/src/schemas/widget-sources.ts +33 -0
  260. package/src/tools/add-dashboard-widget.ts +51 -0
  261. package/src/tools/archive-project.ts +2 -4
  262. package/src/tools/archive-run-plan.ts +2 -4
  263. package/src/tools/archive-scenario.ts +2 -4
  264. package/src/tools/archive-test-suite.ts +2 -4
  265. package/src/tools/assign-prompt-tag.ts +6 -2
  266. package/src/tools/create-agent.ts +1 -1
  267. package/src/tools/create-analytics-report.ts +7 -0
  268. package/src/tools/create-annotation.ts +7 -2
  269. package/src/tools/create-api-key.ts +4 -6
  270. package/src/tools/create-dataset-records.ts +4 -8
  271. package/src/tools/create-dataset.ts +5 -7
  272. package/src/tools/create-evaluator.ts +3 -5
  273. package/src/tools/create-project.ts +2 -2
  274. package/src/tools/create-prompt-tag.ts +2 -4
  275. package/src/tools/create-prompt.ts +9 -18
  276. package/src/tools/create-scenario.ts +5 -7
  277. package/src/tools/create-test-suite.ts +4 -9
  278. package/src/tools/create-trigger.ts +55 -0
  279. package/src/tools/dashboard-widget-queries.ts +45 -0
  280. package/src/tools/dashboards.ts +104 -0
  281. package/src/tools/delete-agent.ts +1 -1
  282. package/src/tools/delete-analytics-report.ts +7 -0
  283. package/src/tools/delete-annotation.ts +1 -1
  284. package/src/tools/delete-dataset-records.ts +4 -8
  285. package/src/tools/delete-dataset.ts +3 -7
  286. package/src/tools/delete-evaluator.ts +4 -6
  287. package/src/tools/delete-prompt-tag.ts +2 -4
  288. package/src/tools/delete-workflow.ts +1 -1
  289. package/src/tools/discover-evaluator-schema.ts +34 -25
  290. package/src/tools/discover-scenario-schema.ts +41 -21
  291. package/src/tools/discover-schema.ts +149 -27
  292. package/src/tools/experiment-run-status.ts +2 -8
  293. package/src/tools/format-dataset-mutation.ts +5 -10
  294. package/src/tools/format-run-plan.ts +7 -21
  295. package/src/tools/format-scenario.ts +2 -4
  296. package/src/tools/format-suite-details.ts +6 -12
  297. package/src/tools/get-agent.ts +15 -6
  298. package/src/tools/get-analytics-report.ts +23 -0
  299. package/src/tools/get-analytics.ts +52 -46
  300. package/src/tools/get-dataset.ts +34 -16
  301. package/src/tools/get-evaluator.ts +4 -11
  302. package/src/tools/get-experiment-results.ts +219 -186
  303. package/src/tools/get-project.ts +2 -4
  304. package/src/tools/get-prompt.ts +63 -84
  305. package/src/tools/get-run-plan.ts +4 -6
  306. package/src/tools/get-scenario.ts +4 -6
  307. package/src/tools/get-simulation-run.ts +27 -24
  308. package/src/tools/get-test-suite.ts +2 -5
  309. package/src/tools/get-trace.ts +16 -17
  310. package/src/tools/get-workflow.ts +1 -1
  311. package/src/tools/list-agents.ts +1 -1
  312. package/src/tools/list-analytics-reports.ts +27 -0
  313. package/src/tools/list-annotations.ts +8 -5
  314. package/src/tools/list-api-keys.ts +7 -2
  315. package/src/tools/list-dashboards.ts +20 -21
  316. package/src/tools/list-dataset-records.ts +8 -6
  317. package/src/tools/list-datasets.ts +12 -23
  318. package/src/tools/list-evaluators.ts +4 -8
  319. package/src/tools/list-experiment-runs.ts +5 -9
  320. package/src/tools/list-experiments.ts +5 -11
  321. package/src/tools/list-model-providers.ts +3 -5
  322. package/src/tools/list-projects.ts +2 -4
  323. package/src/tools/list-prompt-tags.ts +3 -3
  324. package/src/tools/list-prompts.ts +4 -8
  325. package/src/tools/list-run-plans.ts +2 -2
  326. package/src/tools/list-scenarios.ts +14 -15
  327. package/src/tools/list-simulation-runs.ts +8 -4
  328. package/src/tools/list-test-suites.ts +1 -1
  329. package/src/tools/list-trigger-fires.ts +17 -0
  330. package/src/tools/list-triggers.ts +39 -0
  331. package/src/tools/list-workflows.ts +2 -4
  332. package/src/tools/rename-prompt-tag.ts +1 -1
  333. package/src/tools/rename-test-suite.ts +2 -5
  334. package/src/tools/report-issue.ts +7 -9
  335. package/src/tools/rerun-run-plan.ts +2 -5
  336. package/src/tools/revoke-api-key.ts +2 -4
  337. package/src/tools/run-agent.ts +29 -26
  338. package/src/tools/run-experiment.ts +37 -34
  339. package/src/tools/run-plan.ts +4 -7
  340. package/src/tools/run-query.ts +47 -0
  341. package/src/tools/run-test-suite.ts +4 -7
  342. package/src/tools/run-workflow.ts +2 -5
  343. package/src/tools/search-traces.ts +161 -47
  344. package/src/tools/set-model-provider.ts +4 -6
  345. package/src/tools/test-agent.ts +2 -2
  346. package/src/tools/update-agent.ts +1 -1
  347. package/src/tools/update-dashboard-widget.ts +86 -0
  348. package/src/tools/update-dataset-record.ts +1 -1
  349. package/src/tools/update-dataset.ts +5 -7
  350. package/src/tools/update-evaluator.ts +3 -5
  351. package/src/tools/update-project.ts +1 -1
  352. package/src/tools/update-prompt.ts +44 -59
  353. package/src/tools/update-scenario.ts +5 -7
  354. package/src/tools/update-test-suite.ts +6 -11
  355. package/src/utils/date-parsing.ts +1 -1
  356. package/src/utils/escape-markdown.ts +3 -8
  357. package/src/utils/format-evaluations.ts +6 -6
  358. package/src/utils/markdown-table.ts +58 -0
  359. package/src/utils/trace-id-shape.ts +20 -0
  360. package/tsconfig.app.json +24 -0
  361. package/tsconfig.json +14 -12
  362. package/uv.lock +667 -341
  363. package/vitest.integration.config.js +10 -0
  364. package/vitest.scenario.config.js +9 -0
  365. package/.eslintrc.cjs +0 -36
  366. package/dist/chunk-AN4OUA4D.js +0 -86
  367. package/dist/chunk-GELJ2MQR.js +0 -226
  368. package/dist/chunk-K37ZNPXK.js +0 -24
  369. package/dist/chunk-S4GVX36W.js +0 -39
  370. package/dist/config.d.ts +0 -17
  371. package/dist/create-dashboard-Z76AIPLS.js +0 -19
  372. package/dist/create-mcp-server.d.ts +0 -10
  373. package/dist/delete-dashboard-LYMRPF27.js +0 -19
  374. package/dist/delete-evaluator-RKYC4TNB.js +0 -16
  375. package/dist/discover-evaluator-schema-ESSHQGPY.js +0 -1912
  376. package/dist/get-analytics-R6AZD3AW.js +0 -73
  377. package/dist/get-dashboard-P4WWVHFU.js +0 -30
  378. package/dist/get-dataset-AKTYSQDI.js +0 -49
  379. package/dist/index.d.ts +0 -2
  380. package/dist/langwatch-api-dashboards-2KJL2M42.js +0 -18
  381. package/dist/langwatch-api-secrets-OEBD6GBU.js +0 -41
  382. package/dist/langwatch-api-triggers-5E6DDGX6.js +0 -31
  383. package/dist/list-dashboards-Z3BZKDGF.js +0 -33
  384. package/dist/run-agent-CAGWGTYT.js +0 -72
  385. package/dist/search-traces-4XTJYXIK.js +0 -79
  386. package/src/__tests__/agent-tools.unit.test.ts +0 -257
  387. package/src/__tests__/all-tools.integration.test.ts +0 -1872
  388. package/src/__tests__/config.unit.test.ts +0 -202
  389. package/src/__tests__/datasets.integration.test.ts +0 -620
  390. package/src/__tests__/datasets.unit.test.ts +0 -244
  391. package/src/__tests__/date-parsing.unit.test.ts +0 -78
  392. package/src/__tests__/discover-evaluator-schema.unit.test.ts +0 -89
  393. package/src/__tests__/discover-schema.unit.test.ts +0 -118
  394. package/src/__tests__/docker.integration.test.ts +0 -318
  395. package/src/__tests__/documentation-fetch-security.integration.test.ts +0 -182
  396. package/src/__tests__/evaluator-tools.unit.test.ts +0 -262
  397. package/src/__tests__/experiment-list-tools.unit.test.ts +0 -157
  398. package/src/__tests__/experiment-status.unit.test.ts +0 -194
  399. package/src/__tests__/feature-map-tool-drift.unit.test.ts +0 -93
  400. package/src/__tests__/get-experiment-results.unit.test.ts +0 -313
  401. package/src/__tests__/http-security-origin.unit.test.ts +0 -97
  402. package/src/__tests__/http-security-sessions.unit.test.ts +0 -209
  403. package/src/__tests__/http-security-verifier.unit.test.ts +0 -248
  404. package/src/__tests__/http-server-allocation.integration.test.ts +0 -279
  405. package/src/__tests__/http-server-origin.integration.test.ts +0 -169
  406. package/src/__tests__/http-server-session-auth.integration.test.ts +0 -341
  407. package/src/__tests__/http-transport.integration.test.ts +0 -395
  408. package/src/__tests__/integration.integration.test.ts +0 -284
  409. package/src/__tests__/langwatch-api.unit.test.ts +0 -661
  410. package/src/__tests__/model-provider-tools.unit.test.ts +0 -190
  411. package/src/__tests__/project-api-key-tools.unit.test.ts +0 -451
  412. package/src/__tests__/prompt-response-types.unit.test.ts +0 -51
  413. package/src/__tests__/prompts.integration.test.ts +0 -736
  414. package/src/__tests__/public-http-request-security.integration.test.ts +0 -118
  415. package/src/__tests__/report-issue.integration.test.ts +0 -147
  416. package/src/__tests__/run-plan-tools.unit.test.ts +0 -565
  417. package/src/__tests__/scenario-tools.integration.test.ts +0 -286
  418. package/src/__tests__/scenario-tools.unit.test.ts +0 -207
  419. package/src/__tests__/schemas.unit.test.ts +0 -85
  420. package/src/__tests__/simulation-run-tools.unit.test.ts +0 -116
  421. package/src/__tests__/support/http-server-harness.ts +0 -138
  422. package/src/__tests__/test-suite-tools.unit.test.ts +0 -615
  423. package/src/__tests__/tools.integration.test.ts +0 -896
  424. package/src/schemas/filter-fields.ts +0 -119
  425. package/src/schemas/index.ts +0 -9
  426. package/src/tools/create-dashboard.ts +0 -7
  427. package/src/tools/delete-dashboard.ts +0 -7
  428. package/src/tools/get-dashboard.ts +0 -22
@@ -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
- });