@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.
- package/.gitignore +7 -0
- package/.npmignore +41 -0
- package/CHANGELOG.md +218 -201
- package/CONTRIBUTING.md +10 -0
- package/README.md +70 -68
- package/dist/add-dashboard-widget-55UI6H22.js +44 -0
- package/dist/add-dashboard-widget-MJHIC3CC.js +45 -0
- package/dist/{archive-project-W3IKI7UL.js → archive-project-K5UD7A6X.js} +4 -4
- package/dist/archive-project-L4PAKT34.js +19 -0
- package/dist/{archive-run-plan-474OKHHU.js → archive-run-plan-5HSQG2HX.js} +4 -4
- package/dist/archive-run-plan-AOKNYQIO.js +15 -0
- package/dist/archive-scenario-2CWJW2VD.js +19 -0
- package/dist/{archive-scenario-V5UNYK26.js → archive-scenario-5IJQESHH.js} +4 -4
- package/dist/{archive-test-suite-O7FLDMXK.js → archive-test-suite-OPB7NFI7.js} +4 -4
- package/dist/archive-test-suite-ZC3TN6VH.js +15 -0
- package/dist/assign-prompt-tag-6DDE6ZZ5.js +23 -0
- package/dist/{assign-prompt-tag-Q46WRB6P.js → assign-prompt-tag-D7O23MJM.js} +8 -4
- package/dist/chunk-3YLN3ATJ.js +241 -0
- package/dist/{chunk-EYXMLE5X.js → chunk-4RX3G62A.js} +56 -47
- package/dist/{chunk-UF6XIGYR.js → chunk-6YZUOD34.js} +2 -5
- package/dist/{chunk-WVI5CJ6S.js → chunk-7AQLPVFL.js} +2 -2
- package/dist/chunk-7CZQDZ2K.js +319 -0
- package/dist/{chunk-RVAE2MEA.js → chunk-7GETGE6B.js} +7 -7
- package/dist/{chunk-OJLYYJWI.js → chunk-AASW2TO2.js} +7 -10
- package/dist/{chunk-QAMP3CGR.js → chunk-B4RFWIUP.js} +4 -4
- package/dist/{chunk-ZXKLPC2E.js → chunk-BIIKE3EI.js} +1 -1
- package/dist/{chunk-G4ZAZTZW.js → chunk-D2HS73VI.js} +7 -10
- package/dist/{chunk-37SMPMQS.js → chunk-DMM3JQ42.js} +7 -2
- package/dist/{chunk-WT4PO4A3.js → chunk-DULFP3MK.js} +10 -7
- package/dist/chunk-EYPJBL7P.js +516 -0
- package/dist/{chunk-NUFPTHKU.js → chunk-FOKVY3BK.js} +1 -1
- package/dist/chunk-GA5JUSPT.js +64 -0
- package/dist/{chunk-UJPXFA26.js → chunk-HHMNZAPD.js} +101 -74
- package/dist/chunk-HLQJXUR6.js +67 -0
- package/dist/{chunk-JTQJRNFJ.js → chunk-KILVK3FS.js} +4 -4
- package/dist/chunk-MOCU5XWF.js +79 -0
- package/dist/chunk-O2WGVCED.js +41 -0
- package/dist/{chunk-HV4DWKBI.js → chunk-O7EMWEVJ.js} +2 -4
- package/dist/{chunk-UFOYFYI7.js → chunk-QXLMHLRC.js} +6 -9
- package/dist/{chunk-QRO35SOM.js → chunk-R5YJIYPF.js} +981 -747
- package/dist/{chunk-JR6DJZOU.js → chunk-RABYBFX5.js} +4 -10
- package/dist/{chunk-SR6QPRIE.js → chunk-RFC2SBEX.js} +7 -13
- package/dist/chunk-SHSVN3SE.js +21 -0
- package/dist/{chunk-CKNAH5TG.js → chunk-U24EACC4.js} +3 -10
- package/dist/{chunk-56OGTS24.js → chunk-VOUGDY2O.js} +4 -11
- package/dist/chunk-WCKZDOGF.js +31 -0
- package/dist/chunk-XQYH52SI.js +129 -0
- package/dist/{chunk-7E4FZIYJ.js → chunk-XUIJYNRJ.js} +1 -1
- package/dist/{chunk-6DJDEXET.js → chunk-YCIUEZVK.js} +6 -12
- package/dist/config-2WQGMAEN.js +16 -0
- package/dist/config.cjs +109 -0
- package/dist/config.js +5 -4
- package/dist/{create-agent-JFJ2K6X3.js → create-agent-6JNLMYQD.js} +4 -4
- package/dist/create-agent-IRZOU4R5.js +23 -0
- package/dist/create-analytics-report-N4L4V5QL.js +19 -0
- package/dist/create-analytics-report-QEHXG7FU.js +18 -0
- package/dist/{create-annotation-EUXXQ6NS.js → create-annotation-CCEGIZ67.js} +10 -5
- package/dist/create-annotation-DTHCMWHB.js +29 -0
- package/dist/{create-api-key-BGBB76JX.js → create-api-key-JKM2DSZO.js} +5 -7
- package/dist/create-api-key-JPEUIXGM.js +24 -0
- package/dist/create-dataset-DJNXYJ5B.js +22 -0
- package/dist/{create-dataset-FD5VAXU4.js → create-dataset-WON4SOTC.js} +6 -6
- package/dist/create-dataset-records-TMEOTLTO.js +20 -0
- package/dist/{create-dataset-records-WQUXXOHI.js → create-dataset-records-UJBW3C7C.js} +5 -7
- package/dist/create-evaluator-N5AEHI4K.js +27 -0
- package/dist/{create-evaluator-6NV33Z52.js → create-evaluator-XYNY2YFY.js} +4 -4
- package/dist/create-mcp-server.cjs +9712 -0
- package/dist/create-mcp-server.js +2757 -10
- package/dist/{create-project-ZQ7TFIO2.js → create-project-EY4XZ7TH.js} +4 -4
- package/dist/create-project-YL345T5L.js +44 -0
- package/dist/{create-prompt-BS6QRCUU.js → create-prompt-JZQNQ7I3.js} +5 -7
- package/dist/create-prompt-ZUMQHHX7.js +37 -0
- package/dist/create-prompt-tag-CJ5FZ436.js +17 -0
- package/dist/{create-prompt-tag-FCQ5ELSQ.js → create-prompt-tag-JFP2Q3PO.js} +3 -3
- package/dist/create-scenario-CQU2SSFB.js +33 -0
- package/dist/{create-scenario-DRPTPXU3.js → create-scenario-S6OWWKJO.js} +5 -5
- package/dist/create-test-suite-OLGCAGJA.js +36 -0
- package/dist/{create-test-suite-ZIZCCFJ4.js → create-test-suite-Y7P2RI4L.js} +6 -6
- package/dist/create-trigger-4CNBTISJ.js +48 -0
- package/dist/create-trigger-RQ653WYF.js +49 -0
- package/dist/dashboards-BA7M6CMO.js +90 -0
- package/dist/dashboards-U663GUIB.js +91 -0
- package/dist/{delete-agent-SA4JMUZF.js → delete-agent-J2Q4DRZ2.js} +4 -4
- package/dist/delete-agent-ZAU4LTXB.js +18 -0
- package/dist/delete-analytics-report-QK6X7P4S.js +19 -0
- package/dist/delete-analytics-report-YUYLDHLL.js +18 -0
- package/dist/delete-annotation-7X4NYXXM.js +17 -0
- package/dist/{delete-annotation-X3KKK7QY.js → delete-annotation-KJOCDLV3.js} +4 -4
- package/dist/{delete-dataset-W6EZ44HY.js → delete-dataset-NXP7ZPPV.js} +5 -7
- package/dist/delete-dataset-RUTZBBVD.js +18 -0
- package/dist/{delete-dataset-records-7CNOTALB.js → delete-dataset-records-4Z7DZCDW.js} +5 -7
- package/dist/delete-dataset-records-75UAR5IS.js +19 -0
- package/dist/delete-evaluator-E3CUNGTQ.js +16 -0
- package/dist/delete-evaluator-RI4GALGX.js +15 -0
- package/dist/delete-prompt-tag-LE3WJPKS.js +17 -0
- package/dist/{delete-prompt-tag-QB3OCZUV.js → delete-prompt-tag-S4KBZTNG.js} +3 -3
- package/dist/delete-workflow-4CZFIQCQ.js +17 -0
- package/dist/{delete-workflow-NG2VXYYM.js → delete-workflow-PWQPDW6H.js} +4 -4
- package/dist/discover-evaluator-schema-UHWA5HVL.js +120 -0
- package/dist/discover-evaluator-schema-XKCLLCB3.js +118 -0
- package/dist/discover-scenario-schema-NOJY6S7E.js +137 -0
- package/dist/{discover-scenario-schema-W7KBXA4J.js → discover-scenario-schema-OOFIQKOG.js} +37 -17
- package/dist/discover-schema-7WHIIATI.js +442 -0
- package/dist/{discover-schema-L6QDPON3.js → discover-schema-J2ANOBKO.js} +181 -186
- package/dist/get-agent-5T3CXQOR.js +69 -0
- package/dist/{get-agent-7THF7HCU.js → get-agent-ZKYQJUQ7.js} +15 -11
- package/dist/get-analytics-4MPEK4KV.js +79 -0
- package/dist/get-analytics-ZLI3A4F5.js +80 -0
- package/dist/get-analytics-report-JCKYMA2S.js +29 -0
- package/dist/get-analytics-report-SKIP2JWH.js +30 -0
- package/dist/get-dataset-APGZBYMH.js +64 -0
- package/dist/get-dataset-LWXDVMVE.js +63 -0
- package/dist/get-evaluator-3A7BJBAB.js +47 -0
- package/dist/{get-evaluator-RZ44UIN6.js → get-evaluator-IWNJINRY.js} +4 -4
- package/dist/get-project-EQVB4LEL.js +26 -0
- package/dist/{get-project-QSQEAB33.js → get-project-RK6AJTEP.js} +4 -4
- package/dist/{get-prompt-GVKXF5NL.js → get-prompt-TLG2T2DS.js} +53 -58
- package/dist/get-prompt-ZER557X3.js +94 -0
- package/dist/get-run-plan-5X7DDJRN.js +58 -0
- package/dist/{get-run-plan-NY7J5GRE.js → get-run-plan-PEHRO5M3.js} +7 -9
- package/dist/{get-scenario-Q3LLQE4T.js → get-scenario-375W5D6I.js} +5 -5
- package/dist/get-scenario-UQX45ZZO.js +37 -0
- package/dist/{get-simulation-run-WINEYDMK.js → get-simulation-run-2NBXJADG.js} +26 -22
- package/dist/get-simulation-run-QZTLGL7W.js +69 -0
- package/dist/{get-test-suite-4HKA7OKS.js → get-test-suite-GVIBDG7K.js} +5 -5
- package/dist/get-test-suite-OODIR3RI.js +48 -0
- package/dist/{get-trace-RCUV7N4O.js → get-trace-AGCHVPJK.js} +11 -13
- package/dist/get-trace-KJLYAZRC.js +45 -0
- package/dist/get-workflow-3TV2YYC5.js +25 -0
- package/dist/{get-workflow-EEOC7Y45.js → get-workflow-7LZSVPP2.js} +4 -4
- package/dist/{http-security-EU3CHTY7.js → http-security-DNLBNQZL.js} +2 -2
- package/dist/{http-server-WCDUARTH.js → http-server-7CCN3EHJ.js} +4249 -325
- package/dist/index.js +59595 -38027
- package/dist/langwatch-api-analytics-reports-6CF6KUET.js +17 -0
- package/dist/langwatch-api-analytics-reports-RPFB5AO5.js +18 -0
- package/dist/{langwatch-api-annotations-6DHVZWUK.js → langwatch-api-annotations-H4AI4TKG.js} +4 -4
- package/dist/langwatch-api-annotations-QSJNUQQS.js +17 -0
- package/dist/langwatch-api-monitors-ENHLOL3S.js +47 -0
- package/dist/{langwatch-api-monitors-6ISFJ7WX.js → langwatch-api-monitors-OHSGADTH.js} +11 -16
- package/dist/langwatch-api-secrets-AGW6UUMD.js +55 -0
- package/dist/langwatch-api-secrets-CLTS766Q.js +56 -0
- package/dist/langwatch-api-triggers-G5UWFNN2.js +22 -0
- package/dist/langwatch-api-triggers-QMR7KEXW.js +23 -0
- package/dist/langwatch-api-workflows-HKTBIKKY.js +15 -0
- package/dist/{langwatch-api-workflows-6UJL5U4Z.js → langwatch-api-workflows-OMROZH2O.js} +4 -4
- package/dist/{list-agents-AMVDQPQL.js → list-agents-BYWDNAHT.js} +4 -4
- package/dist/list-agents-MG24MHZE.js +42 -0
- package/dist/list-analytics-reports-G3EPWGSQ.js +32 -0
- package/dist/list-analytics-reports-PPWIFRES.js +33 -0
- package/dist/list-annotations-QCLGEK5A.js +38 -0
- package/dist/{list-annotations-6PR5NLIC.js → list-annotations-Y4EUBJHG.js} +10 -5
- package/dist/list-api-keys-DAVOBPFK.js +42 -0
- package/dist/{list-api-keys-GVPNBIHQ.js → list-api-keys-JMW5WRXX.js} +10 -5
- package/dist/list-dashboards-2P3VJFRS.js +29 -0
- package/dist/list-dashboards-MW5O3MKH.js +30 -0
- package/dist/list-dataset-records-KEHT7G6Q.js +43 -0
- package/dist/{list-dataset-records-O67HZ35Y.js → list-dataset-records-KMLLMZAZ.js} +14 -10
- package/dist/{list-datasets-WXZL6UMP.js → list-datasets-P3JK2ZHS.js} +9 -13
- package/dist/list-datasets-Z7OOE4KI.js +56 -0
- package/dist/{list-evaluators-PAX72WCN.js → list-evaluators-7RRESBOU.js} +5 -7
- package/dist/list-evaluators-VATJVOHR.js +32 -0
- package/dist/{list-model-providers-VVLZMLCX.js → list-model-providers-OXDRPUCJ.js} +4 -4
- package/dist/list-model-providers-VXQJWDXU.js +35 -0
- package/dist/{list-projects-AQVI6SUK.js → list-projects-HUVK7L3W.js} +5 -7
- package/dist/list-projects-X72ZXVY5.js +40 -0
- package/dist/{list-prompt-tags-WZFBJOHG.js → list-prompt-tags-3I3LWFIR.js} +3 -3
- package/dist/list-prompt-tags-DRHUH2ND.js +23 -0
- package/dist/list-prompts-6DX3QECJ.js +30 -0
- package/dist/{list-prompts-BQOHRHYT.js → list-prompts-PS423RU3.js} +4 -6
- package/dist/list-run-plans-5SXPOV3X.js +45 -0
- package/dist/{list-run-plans-UBAU2DGH.js → list-run-plans-6VNHINPY.js} +5 -5
- package/dist/list-scenarios-EQWCW7Q4.js +45 -0
- package/dist/{list-scenarios-MS4IUIVM.js → list-scenarios-GKNH3YND.js} +13 -11
- package/dist/{list-simulation-runs-D6YF4ZOU.js → list-simulation-runs-KHY7KALI.js} +12 -8
- package/dist/list-simulation-runs-PUDK32C2.js +53 -0
- package/dist/list-test-suites-7H2DRILK.js +34 -0
- package/dist/{list-test-suites-L7R2O6DR.js → list-test-suites-GY56LGLY.js} +4 -4
- package/dist/list-trigger-fires-7RZUP54Y.js +19 -0
- package/dist/list-trigger-fires-OZUF65LI.js +20 -0
- package/dist/list-triggers-BGUCUXTJ.js +45 -0
- package/dist/list-triggers-XC5KMX4C.js +46 -0
- package/dist/list-workflows-55NYC3PC.js +33 -0
- package/dist/{list-workflows-6MV4XJHF.js → list-workflows-HV7FUWZA.js} +5 -7
- package/dist/{rename-prompt-tag-K5LQ4CY6.js → rename-prompt-tag-2I7Z5FCY.js} +3 -3
- package/dist/rename-prompt-tag-SZYTOWDX.js +18 -0
- package/dist/{rename-test-suite-NTYHAO5X.js → rename-test-suite-27AYY33K.js} +4 -4
- package/dist/rename-test-suite-CLIEPZDO.js +20 -0
- package/dist/report-issue-5HSL7CNB.js +924 -0
- package/dist/{report-issue-RHZZ33IS.js → report-issue-N7ND5TM6.js} +107 -67
- package/dist/{rerun-run-plan-MUDWJBE4.js → rerun-run-plan-O3VUZ6CG.js} +5 -5
- package/dist/rerun-run-plan-OJTVVGJI.js +21 -0
- package/dist/{revoke-api-key-6LAZ3QZZ.js → revoke-api-key-BU6BKGCG.js} +4 -4
- package/dist/revoke-api-key-NZNCFOIR.js +15 -0
- package/dist/run-agent-244O4Y5M.js +75 -0
- package/dist/run-agent-HPCGZGYW.js +74 -0
- package/dist/run-plan-2ZPGGZ4H.js +38 -0
- package/dist/{run-plan-T4ZYOZGA.js → run-plan-KSXUYQT3.js} +7 -7
- package/dist/run-query-4LMOQ5UW.js +47 -0
- package/dist/run-query-BQUHIUC2.js +46 -0
- package/dist/{run-test-suite-L4MZ7G4D.js → run-test-suite-P3WLVPXC.js} +6 -6
- package/dist/run-test-suite-UEOMCON6.js +25 -0
- package/dist/run-workflow-EFZ6HG5B.js +36 -0
- package/dist/{run-workflow-DFIS23AP.js → run-workflow-I4IHEN54.js} +4 -4
- package/dist/search-traces-6IR7PR4L.js +170 -0
- package/dist/search-traces-BYML2NQ4.js +171 -0
- package/dist/set-model-provider-RBNGWFVI.js +33 -0
- package/dist/{set-model-provider-XRNMFOVR.js → set-model-provider-SHEVKWRC.js} +4 -4
- package/dist/{update-agent-DUWNYBNA.js → update-agent-PTKTM2B5.js} +4 -4
- package/dist/update-agent-ZPXWRMS2.js +20 -0
- package/dist/update-dashboard-widget-HKCBXH7X.js +62 -0
- package/dist/update-dashboard-widget-IP2TTQ5G.js +63 -0
- package/dist/update-dataset-GHIN6JR3.js +22 -0
- package/dist/{update-dataset-YSOMDCOB.js → update-dataset-IRVMY5PF.js} +6 -6
- package/dist/update-dataset-record-6VI7K2CL.js +19 -0
- package/dist/{update-dataset-record-DEA2ITYR.js → update-dataset-record-Z3L7ACNF.js} +4 -4
- package/dist/update-evaluator-5NLNE6VY.js +24 -0
- package/dist/{update-evaluator-NH7Q5GWV.js → update-evaluator-FEH7H6NS.js} +4 -4
- package/dist/update-project-75H3LDQT.js +24 -0
- package/dist/{update-project-Q26FBSYH.js → update-project-QPR3QX55.js} +4 -4
- package/dist/update-prompt-BCDFTNLK.js +140 -0
- package/dist/{update-prompt-ZQQ2C3KU.js → update-prompt-NPRXYSWA.js} +27 -29
- package/dist/update-scenario-BNOMDCMV.js +34 -0
- package/dist/{update-scenario-WUX34JAR.js → update-scenario-EM5XGMB7.js} +5 -5
- package/package.json +35 -39
- package/pyproject.toml +23 -8
- package/src/config.d.ts +6 -0
- package/src/config.ts +22 -29
- package/src/create-mcp-server.ts +887 -634
- package/src/documentation-fetch.ts +21 -20
- package/src/http-security.ts +166 -135
- package/src/http-server.ts +189 -224
- package/src/index.ts +14 -16
- package/src/langwatch-api-agents.ts +18 -21
- package/src/langwatch-api-analytics-reports.ts +62 -0
- package/src/langwatch-api-annotations.ts +11 -12
- package/src/langwatch-api-api-keys.ts +8 -9
- package/src/langwatch-api-dashboard-widgets.ts +47 -0
- package/src/langwatch-api-dashboards.ts +109 -24
- package/src/langwatch-api-datasets.ts +98 -32
- package/src/langwatch-api-evaluators.ts +13 -19
- package/src/langwatch-api-model-providers.ts +4 -4
- package/src/langwatch-api-monitors.ts +14 -18
- package/src/langwatch-api-projects.ts +10 -9
- package/src/langwatch-api-query.ts +114 -0
- package/src/langwatch-api-run-plans.ts +12 -29
- package/src/langwatch-api-scenarios.ts +9 -11
- package/src/langwatch-api-secrets.ts +31 -24
- package/src/langwatch-api-simulation-runs.ts +6 -8
- package/src/langwatch-api-test-suites.ts +14 -33
- package/src/langwatch-api-triggers.ts +96 -34
- package/src/langwatch-api-workflows.ts +9 -9
- package/src/langwatch-api.ts +118 -124
- package/src/public-http-request.ts +21 -12
- package/src/schemas/analytics-metrics.ts +3 -15
- package/src/schemas/create-dataset.ts +3 -4
- package/src/schemas/run-plan.ts +13 -29
- package/src/schemas/suite-fields.ts +6 -23
- package/src/schemas/triggers.ts +365 -0
- package/src/schemas/widget-sources.ts +33 -0
- package/src/tools/add-dashboard-widget.ts +51 -0
- package/src/tools/archive-project.ts +2 -4
- package/src/tools/archive-run-plan.ts +2 -4
- package/src/tools/archive-scenario.ts +2 -4
- package/src/tools/archive-test-suite.ts +2 -4
- package/src/tools/assign-prompt-tag.ts +6 -2
- package/src/tools/create-agent.ts +1 -1
- package/src/tools/create-analytics-report.ts +7 -0
- package/src/tools/create-annotation.ts +7 -2
- package/src/tools/create-api-key.ts +4 -6
- package/src/tools/create-dataset-records.ts +4 -8
- package/src/tools/create-dataset.ts +5 -7
- package/src/tools/create-evaluator.ts +3 -5
- package/src/tools/create-project.ts +2 -2
- package/src/tools/create-prompt-tag.ts +2 -4
- package/src/tools/create-prompt.ts +9 -18
- package/src/tools/create-scenario.ts +5 -7
- package/src/tools/create-test-suite.ts +4 -9
- package/src/tools/create-trigger.ts +55 -0
- package/src/tools/dashboard-widget-queries.ts +45 -0
- package/src/tools/dashboards.ts +104 -0
- package/src/tools/delete-agent.ts +1 -1
- package/src/tools/delete-analytics-report.ts +7 -0
- package/src/tools/delete-annotation.ts +1 -1
- package/src/tools/delete-dataset-records.ts +4 -8
- package/src/tools/delete-dataset.ts +3 -7
- package/src/tools/delete-evaluator.ts +4 -6
- package/src/tools/delete-prompt-tag.ts +2 -4
- package/src/tools/delete-workflow.ts +1 -1
- package/src/tools/discover-evaluator-schema.ts +34 -25
- package/src/tools/discover-scenario-schema.ts +41 -21
- package/src/tools/discover-schema.ts +149 -27
- package/src/tools/experiment-run-status.ts +2 -8
- package/src/tools/format-dataset-mutation.ts +5 -10
- package/src/tools/format-run-plan.ts +7 -21
- package/src/tools/format-scenario.ts +2 -4
- package/src/tools/format-suite-details.ts +6 -12
- package/src/tools/get-agent.ts +15 -6
- package/src/tools/get-analytics-report.ts +23 -0
- package/src/tools/get-analytics.ts +52 -46
- package/src/tools/get-dataset.ts +34 -16
- package/src/tools/get-evaluator.ts +4 -11
- package/src/tools/get-experiment-results.ts +219 -186
- package/src/tools/get-project.ts +2 -4
- package/src/tools/get-prompt.ts +63 -84
- package/src/tools/get-run-plan.ts +4 -6
- package/src/tools/get-scenario.ts +4 -6
- package/src/tools/get-simulation-run.ts +27 -24
- package/src/tools/get-test-suite.ts +2 -5
- package/src/tools/get-trace.ts +16 -17
- package/src/tools/get-workflow.ts +1 -1
- package/src/tools/list-agents.ts +1 -1
- package/src/tools/list-analytics-reports.ts +27 -0
- package/src/tools/list-annotations.ts +8 -5
- package/src/tools/list-api-keys.ts +7 -2
- package/src/tools/list-dashboards.ts +20 -21
- package/src/tools/list-dataset-records.ts +8 -6
- package/src/tools/list-datasets.ts +12 -23
- package/src/tools/list-evaluators.ts +4 -8
- package/src/tools/list-experiment-runs.ts +5 -9
- package/src/tools/list-experiments.ts +5 -11
- package/src/tools/list-model-providers.ts +3 -5
- package/src/tools/list-projects.ts +2 -4
- package/src/tools/list-prompt-tags.ts +3 -3
- package/src/tools/list-prompts.ts +4 -8
- package/src/tools/list-run-plans.ts +2 -2
- package/src/tools/list-scenarios.ts +14 -15
- package/src/tools/list-simulation-runs.ts +8 -4
- package/src/tools/list-test-suites.ts +1 -1
- package/src/tools/list-trigger-fires.ts +17 -0
- package/src/tools/list-triggers.ts +39 -0
- package/src/tools/list-workflows.ts +2 -4
- package/src/tools/rename-prompt-tag.ts +1 -1
- package/src/tools/rename-test-suite.ts +2 -5
- package/src/tools/report-issue.ts +7 -9
- package/src/tools/rerun-run-plan.ts +2 -5
- package/src/tools/revoke-api-key.ts +2 -4
- package/src/tools/run-agent.ts +29 -26
- package/src/tools/run-experiment.ts +37 -34
- package/src/tools/run-plan.ts +4 -7
- package/src/tools/run-query.ts +47 -0
- package/src/tools/run-test-suite.ts +4 -7
- package/src/tools/run-workflow.ts +2 -5
- package/src/tools/search-traces.ts +161 -47
- package/src/tools/set-model-provider.ts +4 -6
- package/src/tools/test-agent.ts +2 -2
- package/src/tools/update-agent.ts +1 -1
- package/src/tools/update-dashboard-widget.ts +86 -0
- package/src/tools/update-dataset-record.ts +1 -1
- package/src/tools/update-dataset.ts +5 -7
- package/src/tools/update-evaluator.ts +3 -5
- package/src/tools/update-project.ts +1 -1
- package/src/tools/update-prompt.ts +44 -59
- package/src/tools/update-scenario.ts +5 -7
- package/src/tools/update-test-suite.ts +6 -11
- package/src/utils/date-parsing.ts +1 -1
- package/src/utils/escape-markdown.ts +3 -8
- package/src/utils/format-evaluations.ts +6 -6
- package/src/utils/markdown-table.ts +58 -0
- package/src/utils/trace-id-shape.ts +20 -0
- package/tsconfig.app.json +24 -0
- package/tsconfig.json +14 -12
- package/uv.lock +667 -341
- package/vitest.integration.config.js +10 -0
- package/vitest.scenario.config.js +9 -0
- package/.eslintrc.cjs +0 -36
- package/dist/chunk-AN4OUA4D.js +0 -86
- package/dist/chunk-GELJ2MQR.js +0 -226
- package/dist/chunk-K37ZNPXK.js +0 -24
- package/dist/chunk-S4GVX36W.js +0 -39
- package/dist/config.d.ts +0 -17
- package/dist/create-dashboard-Z76AIPLS.js +0 -19
- package/dist/create-mcp-server.d.ts +0 -10
- package/dist/delete-dashboard-LYMRPF27.js +0 -19
- package/dist/delete-evaluator-RKYC4TNB.js +0 -16
- package/dist/discover-evaluator-schema-ESSHQGPY.js +0 -1912
- package/dist/get-analytics-R6AZD3AW.js +0 -73
- package/dist/get-dashboard-P4WWVHFU.js +0 -30
- package/dist/get-dataset-AKTYSQDI.js +0 -49
- package/dist/index.d.ts +0 -2
- package/dist/langwatch-api-dashboards-2KJL2M42.js +0 -18
- package/dist/langwatch-api-secrets-OEBD6GBU.js +0 -41
- package/dist/langwatch-api-triggers-5E6DDGX6.js +0 -31
- package/dist/list-dashboards-Z3BZKDGF.js +0 -33
- package/dist/run-agent-CAGWGTYT.js +0 -72
- package/dist/search-traces-4XTJYXIK.js +0 -79
- package/src/__tests__/agent-tools.unit.test.ts +0 -257
- package/src/__tests__/all-tools.integration.test.ts +0 -1872
- package/src/__tests__/config.unit.test.ts +0 -202
- package/src/__tests__/datasets.integration.test.ts +0 -620
- package/src/__tests__/datasets.unit.test.ts +0 -244
- package/src/__tests__/date-parsing.unit.test.ts +0 -78
- package/src/__tests__/discover-evaluator-schema.unit.test.ts +0 -89
- package/src/__tests__/discover-schema.unit.test.ts +0 -118
- package/src/__tests__/docker.integration.test.ts +0 -318
- package/src/__tests__/documentation-fetch-security.integration.test.ts +0 -182
- package/src/__tests__/evaluator-tools.unit.test.ts +0 -262
- package/src/__tests__/experiment-list-tools.unit.test.ts +0 -157
- package/src/__tests__/experiment-status.unit.test.ts +0 -194
- package/src/__tests__/feature-map-tool-drift.unit.test.ts +0 -93
- package/src/__tests__/get-experiment-results.unit.test.ts +0 -313
- package/src/__tests__/http-security-origin.unit.test.ts +0 -97
- package/src/__tests__/http-security-sessions.unit.test.ts +0 -209
- package/src/__tests__/http-security-verifier.unit.test.ts +0 -248
- package/src/__tests__/http-server-allocation.integration.test.ts +0 -279
- package/src/__tests__/http-server-origin.integration.test.ts +0 -169
- package/src/__tests__/http-server-session-auth.integration.test.ts +0 -341
- package/src/__tests__/http-transport.integration.test.ts +0 -395
- package/src/__tests__/integration.integration.test.ts +0 -284
- package/src/__tests__/langwatch-api.unit.test.ts +0 -661
- package/src/__tests__/model-provider-tools.unit.test.ts +0 -190
- package/src/__tests__/project-api-key-tools.unit.test.ts +0 -451
- package/src/__tests__/prompt-response-types.unit.test.ts +0 -51
- package/src/__tests__/prompts.integration.test.ts +0 -736
- package/src/__tests__/public-http-request-security.integration.test.ts +0 -118
- package/src/__tests__/report-issue.integration.test.ts +0 -147
- package/src/__tests__/run-plan-tools.unit.test.ts +0 -565
- package/src/__tests__/scenario-tools.integration.test.ts +0 -286
- package/src/__tests__/scenario-tools.unit.test.ts +0 -207
- package/src/__tests__/schemas.unit.test.ts +0 -85
- package/src/__tests__/simulation-run-tools.unit.test.ts +0 -116
- package/src/__tests__/support/http-server-harness.ts +0 -138
- package/src/__tests__/test-suite-tools.unit.test.ts +0 -615
- package/src/__tests__/tools.integration.test.ts +0 -896
- package/src/schemas/filter-fields.ts +0 -119
- package/src/schemas/index.ts +0 -9
- package/src/tools/create-dashboard.ts +0 -7
- package/src/tools/delete-dashboard.ts +0 -7
- package/src/tools/get-dashboard.ts +0 -22
|
@@ -5,20 +5,21 @@ const MAX_DOCUMENTATION_BYTES = 2 * 1024 * 1024;
|
|
|
5
5
|
const DOCUMENTATION_TIMEOUT_MS = 30_000;
|
|
6
6
|
const DOCUMENTATION_CONTENT_TYPES = new Set(["text/markdown", "text/plain"]);
|
|
7
7
|
|
|
8
|
-
const DOCUMENTATION_CONFIG: Record<DocumentationKind, { defaultPath: string; namespace: string }> =
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
8
|
+
const DOCUMENTATION_CONFIG: Record<DocumentationKind, { defaultPath: string; namespace: string }> =
|
|
9
|
+
{
|
|
10
|
+
langwatch: {
|
|
11
|
+
defaultPath: "/docs/llms.txt",
|
|
12
|
+
namespace: "/docs",
|
|
13
|
+
},
|
|
14
|
+
scenario: {
|
|
15
|
+
defaultPath: "/scenario/llms.txt",
|
|
16
|
+
namespace: "/scenario",
|
|
17
|
+
},
|
|
18
|
+
};
|
|
18
19
|
|
|
19
20
|
function documentationUrlError(namespace: string): Error {
|
|
20
21
|
return new Error(
|
|
21
|
-
`Only a trusted LangWatch documentation URL under ${TRUSTED_DOCUMENTATION_ORIGIN}${namespace}/ may be fetched
|
|
22
|
+
`Only a trusted LangWatch documentation URL under ${TRUSTED_DOCUMENTATION_ORIGIN}${namespace}/ may be fetched`,
|
|
22
23
|
);
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -45,14 +46,14 @@ export function resolveDocumentationUrl(kind: DocumentationKind, input?: string)
|
|
|
45
46
|
throw documentationUrlError(namespace);
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
url.protocol
|
|
50
|
-
url.hostname
|
|
51
|
-
url.port
|
|
52
|
-
url.username
|
|
53
|
-
url.password
|
|
54
|
-
|
|
55
|
-
) {
|
|
49
|
+
const isTrustedDocumentationUrl =
|
|
50
|
+
url.protocol === "https:" &&
|
|
51
|
+
url.hostname === "langwatch.ai" &&
|
|
52
|
+
url.port === "" &&
|
|
53
|
+
url.username === "" &&
|
|
54
|
+
url.password === "" &&
|
|
55
|
+
url.pathname.startsWith(`${namespace}/`);
|
|
56
|
+
if (!isTrustedDocumentationUrl) {
|
|
56
57
|
throw documentationUrlError(namespace);
|
|
57
58
|
}
|
|
58
59
|
|
|
@@ -67,7 +68,7 @@ export function resolveDocumentationUrl(kind: DocumentationKind, input?: string)
|
|
|
67
68
|
export async function fetchDocumentation(
|
|
68
69
|
kind: DocumentationKind,
|
|
69
70
|
input?: string,
|
|
70
|
-
fetchImplementation: typeof fetch = fetch
|
|
71
|
+
fetchImplementation: typeof fetch = fetch,
|
|
71
72
|
): Promise<string> {
|
|
72
73
|
const url = resolveDocumentationUrl(kind, input);
|
|
73
74
|
const response = await fetchImplementation(url, {
|
package/src/http-security.ts
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Security primitives for the standalone MCP HTTP server.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* database of its own, so it cannot look an API key up locally the way the
|
|
6
|
-
* in-app handler does. These helpers give it the same guarantees over HTTP:
|
|
7
|
-
* an origin allowlist, API key verification against the LangWatch API with a
|
|
8
|
-
* short-lived cache, per-IP rate limiting of failed authentication, and a
|
|
9
|
-
* session store that expires idle sessions and caps concurrency per key.
|
|
2
|
+
* Security primitives for the standalone MCP HTTP server. The standalone server runs on
|
|
3
|
+
* developer machines and inside clusters with no database of its own, so it cannot look an API
|
|
4
|
+
* key up locally the way the in-app handler does.
|
|
10
5
|
*/
|
|
11
6
|
|
|
12
7
|
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
@@ -38,25 +33,18 @@ export function isLoopbackHost(host: string): boolean {
|
|
|
38
33
|
// ---------------------------------------------------------------------------
|
|
39
34
|
|
|
40
35
|
/**
|
|
41
|
-
* Origins accepted without configuration.
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* hostname that resolves to a loopback address, so the browser sends that
|
|
45
|
-
* attacker hostname in the Origin header. It can never send a loopback origin
|
|
46
|
-
* for a page it did not actually load from this machine, which is the case the
|
|
47
|
-
* server exists to serve.
|
|
36
|
+
* Origins accepted without configuration. A DNS rebinding attack reaches the server through an
|
|
37
|
+
* attacker-controlled hostname that resolves to a loopback address, so the browser sends that
|
|
38
|
+
* attacker hostname in the Origin header.
|
|
48
39
|
*/
|
|
49
40
|
const ALWAYS_ALLOWED_ORIGIN_HOSTS = new Set(["localhost", "127.0.0.1", "::1"]);
|
|
50
41
|
|
|
51
42
|
/**
|
|
52
|
-
* Reduces an origin to `scheme://host[:port]` so comparisons ignore trailing
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* and `file://` pages.
|
|
43
|
+
* Reduces an origin to `scheme://host[:port]` so comparisons ignore trailing slashes, paths,
|
|
44
|
+
* and case differences. Returns null for anything that is not a usable origin, including the
|
|
45
|
+
* opaque `null` origin sent by sandboxed frames and `file://` pages.
|
|
56
46
|
*/
|
|
57
|
-
function normalizeOrigin(
|
|
58
|
-
origin: string
|
|
59
|
-
): { origin: string; hostname: string } | null {
|
|
47
|
+
function normalizeOrigin(origin: string): { origin: string; hostname: string } | null {
|
|
60
48
|
let parsed: URL;
|
|
61
49
|
try {
|
|
62
50
|
parsed = new URL(origin.trim());
|
|
@@ -105,21 +93,15 @@ export function isOriginAllowed({
|
|
|
105
93
|
// ---------------------------------------------------------------------------
|
|
106
94
|
|
|
107
95
|
/**
|
|
108
|
-
* Per-process key for deriving identifiers from API keys.
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* are only compared within one process, so they never need to be reproducible
|
|
112
|
-
* across restarts. Keying the digest means that anything which exposes those
|
|
113
|
-
* maps, a heap dump or debug output, still does not let a list of candidate
|
|
114
|
-
* keys be confirmed offline the way a bare digest would.
|
|
96
|
+
* Per-process key for deriving identifiers from API keys. Regenerated on every start. The
|
|
97
|
+
* derived values only index in-memory maps and are only compared within one process, so they
|
|
98
|
+
* never need to be reproducible across restarts.
|
|
115
99
|
*/
|
|
116
100
|
const KEY_DERIVATION_SECRET = randomBytes(32);
|
|
117
101
|
|
|
118
102
|
/** Derives an opaque identifier from an API key, for use as a map key. */
|
|
119
103
|
export function hashApiKey(apiKey: string): string {
|
|
120
|
-
return createHmac("sha256", KEY_DERIVATION_SECRET)
|
|
121
|
-
.update(apiKey)
|
|
122
|
-
.digest("hex");
|
|
104
|
+
return createHmac("sha256", KEY_DERIVATION_SECRET).update(apiKey).digest("hex");
|
|
123
105
|
}
|
|
124
106
|
|
|
125
107
|
/** Constant-time API key comparison over fixed-length digests. */
|
|
@@ -130,20 +112,14 @@ export function apiKeysMatch({
|
|
|
130
112
|
presentedKey: string;
|
|
131
113
|
expectedKey: string;
|
|
132
114
|
}): boolean {
|
|
133
|
-
const presented = createHmac("sha256", KEY_DERIVATION_SECRET)
|
|
134
|
-
|
|
135
|
-
.digest();
|
|
136
|
-
const expected = createHmac("sha256", KEY_DERIVATION_SECRET)
|
|
137
|
-
.update(expectedKey)
|
|
138
|
-
.digest();
|
|
115
|
+
const presented = createHmac("sha256", KEY_DERIVATION_SECRET).update(presentedKey).digest();
|
|
116
|
+
const expected = createHmac("sha256", KEY_DERIVATION_SECRET).update(expectedKey).digest();
|
|
139
117
|
return timingSafeEqual(presented, expected);
|
|
140
118
|
}
|
|
141
119
|
|
|
142
|
-
// ---------------------------------------------------------------------------
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
// Counts reset in whole windows rather than sliding, so a burst straddling a
|
|
146
|
-
// window boundary can reach twice maxRequests. That is acceptable for the
|
|
120
|
+
// --------------------------------------------------------------------------- Rate limiter
|
|
121
|
+
// (fixed window per client address) Counts reset in whole windows rather than sliding, so a
|
|
122
|
+
// burst straddling a window boundary can reach twice maxRequests. That is acceptable for the
|
|
147
123
|
// failed-authentication and OAuth limits this backs.
|
|
148
124
|
// ---------------------------------------------------------------------------
|
|
149
125
|
|
|
@@ -203,7 +179,7 @@ export function createRateLimiter({
|
|
|
203
179
|
* project API key and returns only the identity of the project behind it, so
|
|
204
180
|
* it answers "is this key real" without granting or costing anything else.
|
|
205
181
|
*/
|
|
206
|
-
const VERIFY_PATH = "/api/me/project";
|
|
182
|
+
const VERIFY_PATH = "/api/v1/me/project";
|
|
207
183
|
|
|
208
184
|
export interface ApiKeyVerifier {
|
|
209
185
|
/** True when the LangWatch API recognises this key. */
|
|
@@ -214,6 +190,75 @@ export interface ApiKeyVerifier {
|
|
|
214
190
|
clear(): void;
|
|
215
191
|
}
|
|
216
192
|
|
|
193
|
+
type VerdictCache = Map<string, { valid: boolean; expiresAt: number }>;
|
|
194
|
+
|
|
195
|
+
function sweepVerdicts(cache: VerdictCache): void {
|
|
196
|
+
const now = Date.now();
|
|
197
|
+
for (const [key, entry] of cache) {
|
|
198
|
+
if (now >= entry.expiresAt) cache.delete(key);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function rememberVerdict({
|
|
203
|
+
cache,
|
|
204
|
+
hashed,
|
|
205
|
+
valid,
|
|
206
|
+
maxEntries,
|
|
207
|
+
ttlMs,
|
|
208
|
+
}: {
|
|
209
|
+
cache: VerdictCache;
|
|
210
|
+
hashed: string;
|
|
211
|
+
valid: boolean;
|
|
212
|
+
maxEntries: number;
|
|
213
|
+
ttlMs: number;
|
|
214
|
+
}): void {
|
|
215
|
+
if (cache.size >= maxEntries) {
|
|
216
|
+
sweepVerdicts(cache);
|
|
217
|
+
// Map iteration is insertion ordered, so the first key is the oldest.
|
|
218
|
+
while (cache.size >= maxEntries) {
|
|
219
|
+
const oldest = cache.keys().next();
|
|
220
|
+
if (oldest.done) break;
|
|
221
|
+
cache.delete(oldest.value);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
cache.set(hashed, { valid, expiresAt: Date.now() + ttlMs });
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** The API's verdict on a key: true, false on 401/403, null when it could not answer. */
|
|
228
|
+
async function askVerifyEndpoint({
|
|
229
|
+
endpoint,
|
|
230
|
+
apiKey,
|
|
231
|
+
fetchImpl,
|
|
232
|
+
requestTimeoutMs,
|
|
233
|
+
}: {
|
|
234
|
+
endpoint: string;
|
|
235
|
+
apiKey: string;
|
|
236
|
+
fetchImpl: typeof fetch;
|
|
237
|
+
requestTimeoutMs: number;
|
|
238
|
+
}): Promise<boolean | null> {
|
|
239
|
+
let response: Response;
|
|
240
|
+
try {
|
|
241
|
+
response = await fetchImpl(`${endpoint}${VERIFY_PATH}`, {
|
|
242
|
+
method: "GET",
|
|
243
|
+
headers: { "X-Auth-Token": apiKey },
|
|
244
|
+
// An upstream that accepts the connection and never answers would
|
|
245
|
+
// otherwise leave the in-flight promise unsettled, parking every
|
|
246
|
+
// request for this key behind it until the socket dies.
|
|
247
|
+
signal: AbortSignal.timeout(requestTimeoutMs),
|
|
248
|
+
});
|
|
249
|
+
} catch {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Only the status matters. Releasing the body returns the socket to the
|
|
254
|
+
// pool instead of holding it until garbage collection.
|
|
255
|
+
await response.body?.cancel().catch(() => undefined);
|
|
256
|
+
|
|
257
|
+
if (response.ok) return true;
|
|
258
|
+
if (response.status === 401 || response.status === 403) return false;
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
217
262
|
export function createApiKeyVerifier({
|
|
218
263
|
endpoint,
|
|
219
264
|
positiveTtlMs = 60_000,
|
|
@@ -229,55 +274,20 @@ export function createApiKeyVerifier({
|
|
|
229
274
|
requestTimeoutMs?: number;
|
|
230
275
|
fetchImpl?: typeof fetch;
|
|
231
276
|
}): ApiKeyVerifier {
|
|
232
|
-
const cache = new Map
|
|
277
|
+
const cache: VerdictCache = new Map();
|
|
233
278
|
const inFlight = new Map<string, Promise<boolean>>();
|
|
234
279
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function remember(hashed: string, valid: boolean): void {
|
|
243
|
-
if (cache.size >= maxEntries) {
|
|
244
|
-
sweep();
|
|
245
|
-
// Map iteration is insertion ordered, so the first key is the oldest.
|
|
246
|
-
while (cache.size >= maxEntries) {
|
|
247
|
-
const oldest = cache.keys().next();
|
|
248
|
-
if (oldest.done) break;
|
|
249
|
-
cache.delete(oldest.value);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
cache.set(hashed, {
|
|
280
|
+
const sweep = (): void => sweepVerdicts(cache);
|
|
281
|
+
const remember = (hashed: string, valid: boolean): void =>
|
|
282
|
+
rememberVerdict({
|
|
283
|
+
cache,
|
|
284
|
+
hashed,
|
|
253
285
|
valid,
|
|
254
|
-
|
|
286
|
+
maxEntries,
|
|
287
|
+
ttlMs: valid ? positiveTtlMs : negativeTtlMs,
|
|
255
288
|
});
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
async function askApi(apiKey: string): Promise<boolean | null> {
|
|
259
|
-
let response: Response;
|
|
260
|
-
try {
|
|
261
|
-
response = await fetchImpl(`${endpoint}${VERIFY_PATH}`, {
|
|
262
|
-
method: "GET",
|
|
263
|
-
headers: { "X-Auth-Token": apiKey },
|
|
264
|
-
// An upstream that accepts the connection and never answers would
|
|
265
|
-
// otherwise leave the in-flight promise unsettled, parking every
|
|
266
|
-
// request for this key behind it until the socket dies.
|
|
267
|
-
signal: AbortSignal.timeout(requestTimeoutMs),
|
|
268
|
-
});
|
|
269
|
-
} catch {
|
|
270
|
-
return null;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Only the status matters. Releasing the body returns the socket to the
|
|
274
|
-
// pool instead of holding it until garbage collection.
|
|
275
|
-
await response.body?.cancel().catch(() => undefined);
|
|
276
|
-
|
|
277
|
-
if (response.ok) return true;
|
|
278
|
-
if (response.status === 401 || response.status === 403) return false;
|
|
279
|
-
return null;
|
|
280
|
-
}
|
|
289
|
+
const askApi = (apiKey: string) =>
|
|
290
|
+
askVerifyEndpoint({ endpoint, apiKey, fetchImpl, requestTimeoutMs });
|
|
281
291
|
|
|
282
292
|
return {
|
|
283
293
|
async verify(apiKey) {
|
|
@@ -355,6 +365,67 @@ export interface SessionStore<TTransport> {
|
|
|
355
365
|
readonly size: number;
|
|
356
366
|
}
|
|
357
367
|
|
|
368
|
+
/** What a session store holds: the live sessions and how many each hashed key owns. */
|
|
369
|
+
interface SessionTable<TTransport> {
|
|
370
|
+
sessions: Map<string, SessionRecord<TTransport>>;
|
|
371
|
+
countByKey: Map<string, number>;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function releaseSlot({ countByKey }: { countByKey: Map<string, number> }, apiKey: string): void {
|
|
375
|
+
const hashed = hashApiKey(apiKey);
|
|
376
|
+
const next = (countByKey.get(hashed) ?? 1) - 1;
|
|
377
|
+
if (next <= 0) countByKey.delete(hashed);
|
|
378
|
+
else countByKey.set(hashed, next);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function dropSession<TTransport>({
|
|
382
|
+
table,
|
|
383
|
+
sessionId,
|
|
384
|
+
record,
|
|
385
|
+
}: {
|
|
386
|
+
table: SessionTable<TTransport>;
|
|
387
|
+
sessionId: string;
|
|
388
|
+
record: SessionRecord<TTransport>;
|
|
389
|
+
}): void {
|
|
390
|
+
table.sessions.delete(sessionId);
|
|
391
|
+
releaseSlot(table, record.apiKey);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** Counts a slot for the key now; the session lands on commit, or the slot is given back. */
|
|
395
|
+
function reserveSlot<TTransport>({
|
|
396
|
+
table,
|
|
397
|
+
apiKey,
|
|
398
|
+
}: {
|
|
399
|
+
table: SessionTable<TTransport>;
|
|
400
|
+
apiKey: string;
|
|
401
|
+
}): SessionReservation<TTransport> {
|
|
402
|
+
const hashed = hashApiKey(apiKey);
|
|
403
|
+
table.countByKey.set(hashed, (table.countByKey.get(hashed) ?? 0) + 1);
|
|
404
|
+
let settled = false;
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
commit({ sessionId, transport }) {
|
|
408
|
+
if (settled) return;
|
|
409
|
+
settled = true;
|
|
410
|
+
|
|
411
|
+
const existing = table.sessions.get(sessionId);
|
|
412
|
+
if (existing) dropSession({ table, sessionId, record: existing });
|
|
413
|
+
|
|
414
|
+
// The count already carries this slot from the reservation.
|
|
415
|
+
table.sessions.set(sessionId, {
|
|
416
|
+
transport,
|
|
417
|
+
apiKey,
|
|
418
|
+
lastActivityAt: Date.now(),
|
|
419
|
+
});
|
|
420
|
+
},
|
|
421
|
+
release() {
|
|
422
|
+
if (settled) return;
|
|
423
|
+
settled = true;
|
|
424
|
+
releaseSlot(table, apiKey);
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
358
429
|
export function createSessionStore<TTransport>({
|
|
359
430
|
maxAgeMs,
|
|
360
431
|
closeTransport,
|
|
@@ -367,45 +438,10 @@ export function createSessionStore<TTransport>({
|
|
|
367
438
|
const sessions = new Map<string, SessionRecord<TTransport>>();
|
|
368
439
|
const countByKey = new Map<string, number>();
|
|
369
440
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
else countByKey.set(hashed, next);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function drop(sessionId: string, record: SessionRecord<TTransport>): void {
|
|
378
|
-
sessions.delete(sessionId);
|
|
379
|
-
decrement(record.apiKey);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
function reserve(apiKey: string): SessionReservation<TTransport> {
|
|
383
|
-
const hashed = hashApiKey(apiKey);
|
|
384
|
-
countByKey.set(hashed, (countByKey.get(hashed) ?? 0) + 1);
|
|
385
|
-
let settled = false;
|
|
386
|
-
|
|
387
|
-
return {
|
|
388
|
-
commit({ sessionId, transport }) {
|
|
389
|
-
if (settled) return;
|
|
390
|
-
settled = true;
|
|
391
|
-
|
|
392
|
-
const existing = sessions.get(sessionId);
|
|
393
|
-
if (existing) drop(sessionId, existing);
|
|
394
|
-
|
|
395
|
-
// The count already carries this slot from the reservation.
|
|
396
|
-
sessions.set(sessionId, {
|
|
397
|
-
transport,
|
|
398
|
-
apiKey,
|
|
399
|
-
lastActivityAt: Date.now(),
|
|
400
|
-
});
|
|
401
|
-
},
|
|
402
|
-
release() {
|
|
403
|
-
if (settled) return;
|
|
404
|
-
settled = true;
|
|
405
|
-
decrement(apiKey);
|
|
406
|
-
},
|
|
407
|
-
};
|
|
408
|
-
}
|
|
441
|
+
const table: SessionTable<TTransport> = { sessions, countByKey };
|
|
442
|
+
const reserve = (apiKey: string) => reserveSlot({ table, apiKey });
|
|
443
|
+
const drop = (sessionId: string, record: SessionRecord<TTransport>) =>
|
|
444
|
+
dropSession({ table, sessionId, record });
|
|
409
445
|
|
|
410
446
|
return {
|
|
411
447
|
get(sessionId) {
|
|
@@ -454,13 +490,8 @@ export interface OAuthTokenEntry {
|
|
|
454
490
|
}
|
|
455
491
|
|
|
456
492
|
/**
|
|
457
|
-
* Makes room for one more token before it is issued.
|
|
458
|
-
*
|
|
459
|
-
* Tokens outlive the requests that created them, so a key asking for them in a
|
|
460
|
-
* loop would otherwise retain entries until each expired. The per-address rate
|
|
461
|
-
* limit does not bound this on its own, because one key can be presented from
|
|
462
|
-
* many addresses. Expired entries anywhere in the map are dropped on the way
|
|
463
|
-
* past.
|
|
493
|
+
* Makes room for one more token before it is issued. Tokens outlive the requests that created
|
|
494
|
+
* them, so a key asking for them in a loop would otherwise retain entries until each expired.
|
|
464
495
|
*/
|
|
465
496
|
export function admitOAuthToken({
|
|
466
497
|
apiKey,
|