From 1e1a3a8f8df1f44d66121fe02f2718ca5d548117 Mon Sep 17 00:00:00 2001
From: Danny Avila
Date: Tue, 17 Mar 2026 15:50:36 -0400
Subject: [PATCH 01/98] =?UTF-8?q?=E2=9C=A8=20v0.8.4-rc1=20(#12285)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- App version: v0.8.3 → v0.8.4-rc1
- @librechat/api: 1.7.25 → 1.7.26
- @librechat/client: 0.4.54 → 0.4.55
- librechat-data-provider: 0.8.302 → 0.8.400
- @librechat/data-schemas: 0.0.38 → 0.0.39
---
Dockerfile | 2 +-
Dockerfile.multi | 2 +-
api/package.json | 2 +-
bun.lock | 418 +++++++++++++++------------
client/jest.config.cjs | 2 +-
client/package.json | 2 +-
e2e/jestSetup.js | 2 +-
helm/librechat/Chart.yaml | 4 +-
package-lock.json | 16 +-
package.json | 2 +-
packages/api/package.json | 2 +-
packages/client/package.json | 2 +-
packages/data-provider/package.json | 2 +-
packages/data-provider/src/config.ts | 2 +-
packages/data-schemas/package.json | 2 +-
15 files changed, 263 insertions(+), 199 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index bbff8133da..02bda8a589 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-# v0.8.3
+# v0.8.4-rc1
# Base node image
FROM node:20-alpine AS node
diff --git a/Dockerfile.multi b/Dockerfile.multi
index 53810b5f0a..8e7483e378 100644
--- a/Dockerfile.multi
+++ b/Dockerfile.multi
@@ -1,5 +1,5 @@
# Dockerfile.multi
-# v0.8.3
+# v0.8.4-rc1
# Set configurable max-old-space-size with default
ARG NODE_MAX_OLD_SPACE_SIZE=6144
diff --git a/api/package.json b/api/package.json
index 89a5183ddd..f32de5e778 100644
--- a/api/package.json
+++ b/api/package.json
@@ -1,6 +1,6 @@
{
"name": "@librechat/backend",
- "version": "v0.8.3",
+ "version": "v0.8.4-rc1",
"description": "",
"scripts": {
"start": "echo 'please run this from the root directory'",
diff --git a/bun.lock b/bun.lock
index 39d9641ec4..f6e3228519 100644
--- a/bun.lock
+++ b/bun.lock
@@ -37,7 +37,7 @@
},
"api": {
"name": "@librechat/backend",
- "version": "0.8.3",
+ "version": "0.8.4-rc1",
"dependencies": {
"@anthropic-ai/vertex-sdk": "^0.14.3",
"@aws-sdk/client-bedrock-runtime": "^3.980.0",
@@ -49,13 +49,14 @@
"@google/genai": "^1.19.0",
"@keyv/redis": "^4.3.3",
"@langchain/core": "^0.3.80",
- "@librechat/agents": "^3.1.55",
+ "@librechat/agents": "^3.1.56",
"@librechat/api": "*",
"@librechat/data-schemas": "*",
"@microsoft/microsoft-graph-client": "^3.0.7",
"@modelcontextprotocol/sdk": "^1.27.1",
"@node-saml/passport-saml": "^5.1.0",
"@smithy/node-http-handler": "^4.4.5",
+ "ai-tokenizer": "^1.0.6",
"axios": "^1.13.5",
"bcryptjs": "^2.4.3",
"compression": "^1.8.1",
@@ -71,7 +72,7 @@
"express-rate-limit": "^8.3.0",
"express-session": "^1.18.2",
"express-static-gzip": "^2.2.0",
- "file-type": "^18.7.0",
+ "file-type": "^21.3.2",
"firebase": "^11.0.2",
"form-data": "^4.0.4",
"handlebars": "^4.7.7",
@@ -111,10 +112,9 @@
"pdfjs-dist": "^5.4.624",
"rate-limit-redis": "^4.2.0",
"sharp": "^0.33.5",
- "tiktoken": "^1.0.15",
"traverse": "^0.6.7",
"ua-parser-js": "^1.0.36",
- "undici": "^7.18.2",
+ "undici": "^7.24.1",
"winston": "^3.11.0",
"winston-daily-rotate-file": "^5.0.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
@@ -129,7 +129,7 @@
},
"client": {
"name": "@librechat/frontend",
- "version": "0.8.3",
+ "version": "0.8.4-rc1",
"dependencies": {
"@ariakit/react": "^0.4.15",
"@ariakit/react-core": "^0.4.17",
@@ -263,7 +263,7 @@
},
"packages/api": {
"name": "@librechat/api",
- "version": "1.7.25",
+ "version": "1.7.26",
"devDependencies": {
"@babel/preset-env": "^7.21.5",
"@babel/preset-react": "^7.18.6",
@@ -307,10 +307,11 @@
"@google/genai": "^1.19.0",
"@keyv/redis": "^4.3.3",
"@langchain/core": "^0.3.80",
- "@librechat/agents": "^3.1.55",
+ "@librechat/agents": "^3.1.56",
"@librechat/data-schemas": "*",
"@modelcontextprotocol/sdk": "^1.27.1",
"@smithy/node-http-handler": "^4.4.5",
+ "ai-tokenizer": "^1.0.6",
"axios": "^1.13.5",
"connect-redis": "^8.1.0",
"eventsource": "^3.0.2",
@@ -333,14 +334,13 @@
"node-fetch": "2.7.0",
"pdfjs-dist": "^5.4.624",
"rate-limit-redis": "^4.2.0",
- "tiktoken": "^1.0.15",
- "undici": "^7.18.2",
+ "undici": "^7.24.1",
"zod": "^3.22.4",
},
},
"packages/client": {
"name": "@librechat/client",
- "version": "0.4.54",
+ "version": "0.4.55",
"devDependencies": {
"@babel/core": "^7.28.5",
"@babel/preset-env": "^7.28.5",
@@ -428,7 +428,7 @@
},
"packages/data-provider": {
"name": "librechat-data-provider",
- "version": "0.8.302",
+ "version": "0.8.400",
"dependencies": {
"axios": "^1.13.5",
"dayjs": "^1.11.13",
@@ -465,7 +465,7 @@
},
"packages/data-schemas": {
"name": "@librechat/data-schemas",
- "version": "0.0.38",
+ "version": "0.0.39",
"devDependencies": {
"@rollup/plugin-alias": "^5.1.0",
"@rollup/plugin-commonjs": "^29.0.0",
@@ -917,6 +917,8 @@
"@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="],
+ "@borewit/text-codec": ["@borewit/text-codec@0.2.2", "", {}, "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ=="],
+
"@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="],
"@cfworker/json-schema": ["@cfworker/json-schema@4.1.1", "", {}, "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og=="],
@@ -1485,7 +1487,7 @@
"@lezer/lr": ["@lezer/lr@1.4.2", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA=="],
- "@librechat/agents": ["@librechat/agents@3.1.55", "", { "dependencies": { "@anthropic-ai/sdk": "^0.73.0", "@aws-sdk/client-bedrock-runtime": "^3.980.0", "@langchain/anthropic": "^0.3.26", "@langchain/aws": "^0.1.15", "@langchain/core": "^0.3.80", "@langchain/deepseek": "^0.0.2", "@langchain/google-genai": "^0.2.18", "@langchain/google-vertexai": "^0.2.18", "@langchain/langgraph": "^0.4.9", "@langchain/mistralai": "^0.2.1", "@langchain/openai": "0.5.18", "@langchain/textsplitters": "^0.1.0", "@langchain/xai": "^0.0.3", "@langfuse/langchain": "^4.3.0", "@langfuse/otel": "^4.3.0", "@langfuse/tracing": "^4.3.0", "@opentelemetry/sdk-node": "^0.207.0", "@scarf/scarf": "^1.4.0", "axios": "^1.13.5", "cheerio": "^1.0.0", "dotenv": "^16.4.7", "https-proxy-agent": "^7.0.6", "mathjs": "^15.1.0", "nanoid": "^3.3.7", "okapibm25": "^1.4.1", "openai": "5.8.2" } }, "sha512-impxeKpCDlPkAVQFWnA6u6xkxDSBR/+H8uYq7rZomBeu0rUh/OhJLiI1fAwPhKXP33udNtHA8GyDi0QJj78R9w=="],
+ "@librechat/agents": ["@librechat/agents@3.1.56", "", { "dependencies": { "@anthropic-ai/sdk": "^0.73.0", "@aws-sdk/client-bedrock-runtime": "^3.980.0", "@langchain/anthropic": "^0.3.26", "@langchain/aws": "^0.1.15", "@langchain/core": "^0.3.80", "@langchain/deepseek": "^0.0.2", "@langchain/google-genai": "^0.2.18", "@langchain/google-vertexai": "^0.2.18", "@langchain/langgraph": "^0.4.9", "@langchain/mistralai": "^0.2.1", "@langchain/openai": "0.5.18", "@langchain/textsplitters": "^0.1.0", "@langchain/xai": "^0.0.3", "@langfuse/langchain": "^4.3.0", "@langfuse/otel": "^4.3.0", "@langfuse/tracing": "^4.3.0", "@opentelemetry/sdk-node": "^0.207.0", "@scarf/scarf": "^1.4.0", "ai-tokenizer": "^1.0.6", "axios": "^1.13.5", "cheerio": "^1.0.0", "dotenv": "^16.4.7", "https-proxy-agent": "^7.0.6", "mathjs": "^15.1.0", "nanoid": "^3.3.7", "okapibm25": "^1.4.1", "openai": "5.8.2" } }, "sha512-HJJwRnLM4XKpTWB4/wPDJR+iegyKBVUwqj7A8QHqzEcHzjKJDTr3wBPxZVH1tagGr6/mbbnErOJ14cH1OSNmpA=="],
"@librechat/api": ["@librechat/api@workspace:packages/api"],
@@ -1589,11 +1591,11 @@
"@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "import-in-the-middle": "^2.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-y6eeli9+TLKnznrR8AZlQMSJT7wILpXH+6EYq5Vf/4Ao+huI7EedxQHwRgVUOMLFbe7VFDvHJrX9/f4lcwnJsA=="],
- "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.208.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-transformer": "0.208.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA=="],
+ "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.207.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-transformer": "0.207.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-4RQluMVVGMrHok/3SVeSJ6EnRNkA2MINcX88sh+d/7DjGUrewW/WT88IsMEci0wUM+5ykTpPPNbEOoW+jwHnbw=="],
"@opentelemetry/otlp-grpc-exporter-base": ["@opentelemetry/otlp-grpc-exporter-base@0.207.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-exporter-base": "0.207.0", "@opentelemetry/otlp-transformer": "0.207.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-eKFjKNdsPed4q9yYqeI5gBTLjXxDM/8jwhiC0icw3zKxHVGBySoDsed5J5q/PGY/3quzenTr3FiTxA3NiNT+nw=="],
- "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.208.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.208.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.208.0", "@opentelemetry/sdk-metrics": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ=="],
+ "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.207.0", "@opentelemetry/sdk-metrics": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+6DRZLqM02uTIY5GASMZWUwr52sLfNiEe20+OEaZKhztCs3+2LxoTjb6JxFRd9q1qNqckXKYlUKjbH/AhG8/ZA=="],
"@opentelemetry/propagator-b3": ["@opentelemetry/propagator-b3@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-9CrbTLFi5Ee4uepxg2qlpQIozoJuoAZU5sKMx0Mn7Oh+p7UrgCiEV6C02FOxxdYVRRFQVCinYR8Kf6eMSQsIsw=="],
@@ -1813,25 +1815,25 @@
"@rollup/pluginutils": ["@rollup/pluginutils@5.1.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^2.3.1" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" } }, "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g=="],
- "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.37.0", "", { "os": "android", "cpu": "arm" }, "sha512-l7StVw6WAa8l3vA1ov80jyetOAEo1FtHvZDbzXDO/02Sq/QVvqlHkYoFwDJPIMj0GKiistsBudfx5tGFnwYWDQ=="],
+ "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="],
- "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.37.0", "", { "os": "android", "cpu": "arm64" }, "sha512-6U3SlVyMxezt8Y+/iEBcbp945uZjJwjZimu76xoG7tO1av9VO691z8PkhzQ85ith2I8R2RddEPeSfcbyPfD4hA=="],
+ "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="],
- "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.37.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+iTQ5YHuGmPt10NTzEyMPbayiNTcOZDWsbxZYR1ZnmLnZxG17ivrPSWFO9j6GalY0+gV3Jtwrrs12DBscxnlYA=="],
+ "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg=="],
- "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.37.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-m8W2UbxLDcmRKVjgl5J/k4B8d7qX2EcJve3Sut7YGrQoPtCIQGPH5AMzuFvYRWZi0FVS0zEY4c8uttPfX6bwYQ=="],
+ "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.59.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w=="],
- "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.37.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-FOMXGmH15OmtQWEt174v9P1JqqhlgYge/bUjIbiVD1nI1NeJ30HYT9SJlZMqdo1uQFyt9cz748F1BHghWaDnVA=="],
+ "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.59.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA=="],
- "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.37.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-SZMxNttjPKvV14Hjck5t70xS3l63sbVwl98g3FlVVx2YIDmfUIy29jQrsw06ewEYQ8lQSuY9mpAPlmgRD2iSsA=="],
+ "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.59.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg=="],
- "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.37.0", "", { "os": "linux", "cpu": "arm" }, "sha512-hhAALKJPidCwZcj+g+iN+38SIOkhK2a9bqtJR+EtyxrKKSt1ynCBeqrQy31z0oWU6thRZzdx53hVgEbRkuI19w=="],
+ "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw=="],
- "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.37.0", "", { "os": "linux", "cpu": "arm" }, "sha512-jUb/kmn/Gd8epbHKEqkRAxq5c2EwRt0DqhSGWjPFxLeFvldFdHQs/n8lQ9x85oAeVb6bHcS8irhTJX2FCOd8Ag=="],
+ "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA=="],
- "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.37.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-oNrJxcQT9IcbcmKlkF+Yz2tmOxZgG9D9GRq+1OE6XCQwCVwxixYAa38Z8qqPzQvzt1FCfmrHX03E0pWoXm1DqA=="],
+ "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA=="],
- "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.37.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-pfxLBMls+28Ey2enpX3JvjEjaJMBX5XlPCZNGxj4kdJyHduPBXtxYeb8alo0a7bqOoWZW2uKynhHxF/MWoHaGQ=="],
+ "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg=="],
@@ -1845,27 +1847,27 @@
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA=="],
- "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.37.0", "", { "os": "linux", "cpu": "none" }, "sha512-PpWwHMPCVpFZLTfLq7EWJWvrmEuLdGn1GMYcm5MV7PaRgwCEYJAwiN94uBuZev0/J/hFIIJCsYw4nLmXA9J7Pw=="],
+ "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg=="],
- "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.37.0", "", { "os": "linux", "cpu": "none" }, "sha512-DTNwl6a3CfhGTAOYZ4KtYbdS8b+275LSLqJVJIrPa5/JuIufWWZ/QFvkxp52gpmguN95eujrM68ZG+zVxa8zHA=="],
+ "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg=="],
- "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.37.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-hZDDU5fgWvDdHFuExN1gBOhCuzo/8TMpidfOR+1cPZJflcEzXdCy1LjnklQdW8/Et9sryOPJAKAQRw8Jq7Tg+A=="],
+ "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.59.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w=="],
- "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.37.0", "", { "os": "linux", "cpu": "x64" }, "sha512-pKivGpgJM5g8dwj0ywBwe/HeVAUSuVVJhUTa/URXjxvoyTT/AxsLTAbkHkDHG7qQxLoW2s3apEIl26uUe08LVQ=="],
+ "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg=="],
- "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.37.0", "", { "os": "linux", "cpu": "x64" }, "sha512-E2lPrLKE8sQbY/2bEkVTGDEk4/49UYRVWgj90MY8yPjpnGBQ+Xi1Qnr7b7UIWw1NOggdFQFOLZ8+5CzCiz143w=="],
+ "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg=="],
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.59.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.59.0", "", { "os": "none", "cpu": "arm64" }, "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA=="],
- "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.37.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Jm7biMazjNzTU4PrQtr7VS8ibeys9Pn29/1bm4ph7CP2kf21950LgN+BaE2mJ1QujnvOc6p54eWWiVvn05SOBg=="],
+ "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.59.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A=="],
- "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.37.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-e3/1SFm1OjefWICB2Ucstg2dxYDkDTZGDYgwufcbsxTHyqQps1UQf33dFEChBNmeSsTOyrjw2JJq0zbG5GF6RA=="],
+ "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.59.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA=="],
- "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.37.0", "", { "os": "win32", "cpu": "x64" }, "sha512-LWbXUBwn/bcLx2sSsqy7pK5o+Nr+VCoRoAohfJ5C/aBio9nfJmGQqHAhU6pwxV/RmyTk5AqdySma7uwWGlmeuA=="],
+ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="],
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
@@ -2009,6 +2011,8 @@
"@testing-library/user-event": ["@testing-library/user-event@14.5.2", "", { "peerDependencies": { "@testing-library/dom": ">=7.21.4" } }, "sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ=="],
+ "@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="],
+
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
"@tsconfig/node10": ["@tsconfig/node10@1.0.11", "", {}, "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw=="],
@@ -2151,7 +2155,7 @@
"@types/multer": ["@types/multer@1.4.13", "", { "dependencies": { "@types/express": "*" } }, "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw=="],
- "@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+ "@types/node": ["@types/node@20.19.37", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw=="],
"@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
@@ -2295,6 +2299,8 @@
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
+ "ai-tokenizer": ["ai-tokenizer@1.0.6", "", { "peerDependencies": { "ai": "^5.0.0" }, "optionalPeers": ["ai"] }, "sha512-GaakQFxen0pRH/HIA4v68ZM40llCH27HUYUSBLK+gVuZ57e53pYJe1xFvSTj4sJJjbWU92m1X6NjPWyeWkFDow=="],
+
"ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="],
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" }, "peerDependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
@@ -2755,7 +2761,7 @@
"date-fns": ["date-fns@3.3.1", "", {}, "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw=="],
- "dayjs": ["dayjs@1.11.13", "", {}, "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="],
+ "dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
@@ -3037,7 +3043,7 @@
"file-stream-rotator": ["file-stream-rotator@0.6.1", "", { "dependencies": { "moment": "^2.29.1" } }, "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ=="],
- "file-type": ["file-type@18.7.0", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.2", "strtok3": "^7.0.0", "token-types": "^5.0.1" } }, "sha512-ihHtXRzXEziMrQ56VSgU7wkxh55iNchFkosu7Y9/S+tXHdKyrGjVK0ujbqNnsxzea+78MaLhN6PGmfYSAv1ACw=="],
+ "file-type": ["file-type@21.3.3", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-pNwbwz8c3aZ+GvbJnIsCnDjKvgCZLHxkFWLEFxU3RMa+Ey++ZSEfisvsWQMcdys6PpxQjWUOIDi1fifXsW3YRg=="],
"filelist": ["filelist@1.0.4", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q=="],
@@ -3839,7 +3845,7 @@
"mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
- "nanoid": ["nanoid@3.3.8", "", { "bin": "bin/nanoid.cjs" }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="],
+ "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"napi-postinstall": ["napi-postinstall@0.3.4", "", { "bin": "lib/cli.js" }, "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ=="],
@@ -4021,8 +4027,6 @@
"pdfjs-dist": ["pdfjs-dist@5.5.207", "", { "optionalDependencies": { "@napi-rs/canvas": "^0.1.95", "node-readable-to-web-readable-stream": "^0.4.2" } }, "sha512-WMqqw06w1vUt9ZfT0gOFhMf3wHsWhaCrxGrckGs5Cci6ybDW87IvPaOd2pnBwT6BJuP/CzXDZxjFgmSULLdsdw=="],
- "peek-readable": ["peek-readable@5.0.0", "", {}, "sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A=="],
-
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
@@ -4297,8 +4301,6 @@
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
- "readable-web-to-node-stream": ["readable-web-to-node-stream@3.0.2", "", { "dependencies": { "readable-stream": "^3.6.0" } }, "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw=="],
-
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
"recoil": ["recoil@0.7.7", "", { "dependencies": { "hamt_plus": "1.0.2" }, "peerDependencies": { "react": ">=16.13.1" } }, "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ=="],
@@ -4379,7 +4381,7 @@
"robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="],
- "rollup": ["rollup@4.37.0", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.37.0", "@rollup/rollup-android-arm64": "4.37.0", "@rollup/rollup-darwin-arm64": "4.37.0", "@rollup/rollup-darwin-x64": "4.37.0", "@rollup/rollup-freebsd-arm64": "4.37.0", "@rollup/rollup-freebsd-x64": "4.37.0", "@rollup/rollup-linux-arm-gnueabihf": "4.37.0", "@rollup/rollup-linux-arm-musleabihf": "4.37.0", "@rollup/rollup-linux-arm64-gnu": "4.37.0", "@rollup/rollup-linux-arm64-musl": "4.37.0", "@rollup/rollup-linux-loongarch64-gnu": "4.37.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.37.0", "@rollup/rollup-linux-riscv64-gnu": "4.37.0", "@rollup/rollup-linux-riscv64-musl": "4.37.0", "@rollup/rollup-linux-s390x-gnu": "4.37.0", "@rollup/rollup-linux-x64-gnu": "4.37.0", "@rollup/rollup-linux-x64-musl": "4.37.0", "@rollup/rollup-win32-arm64-msvc": "4.37.0", "@rollup/rollup-win32-ia32-msvc": "4.37.0", "@rollup/rollup-win32-x64-msvc": "4.37.0", "fsevents": "~2.3.2" }, "bin": "dist/bin/rollup" }, "sha512-iAtQy/L4QFU+rTJ1YUjXqJOJzuwEghqWzCEYD2FEghT7Gsy1VdABntrO4CLopA5IkflTyqNiLNwPcOJ3S7UKLg=="],
+ "rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
"rollup-plugin-peer-deps-external": ["rollup-plugin-peer-deps-external@2.2.4", "", { "peerDependencies": { "rollup": "*" } }, "sha512-AWdukIM1+k5JDdAqV/Cxd+nejvno2FVLVeZ74NKggm3Q5s9cbbcOgUPGdbxPi4BXu7xGaZ8HG12F+thImYu/0g=="],
@@ -4563,7 +4565,7 @@
"strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="],
- "strtok3": ["strtok3@7.0.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^5.0.0" } }, "sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ=="],
+ "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="],
"style-inject": ["style-inject@0.3.0", "", {}, "sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw=="],
@@ -4627,8 +4629,6 @@
"thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
- "tiktoken": ["tiktoken@1.0.15", "", {}, "sha512-sCsrq/vMWUSEW29CJLNmPvWxlVp7yh2tlkAjpJltIKqp5CKf98ZNpdeHRmAlPVFlGEbswDc6SmI8vz64W/qErw=="],
-
"timers-browserify": ["timers-browserify@2.0.12", "", { "dependencies": { "setimmediate": "^1.0.4" } }, "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ=="],
"tiny-emitter": ["tiny-emitter@2.1.0", "", {}, "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="],
@@ -4651,7 +4651,7 @@
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
- "token-types": ["token-types@5.0.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg=="],
+ "token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="],
"touch": ["touch@3.1.0", "", { "dependencies": { "nopt": "~1.0.10" }, "bin": { "nodetouch": "bin/nodetouch.js" } }, "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA=="],
@@ -4735,15 +4735,17 @@
"uid2": ["uid2@0.0.4", "", {}, "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA=="],
+ "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
+
"unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
"undefsafe": ["undefsafe@2.0.5", "", {}, "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA=="],
"underscore": ["underscore@1.13.8", "", {}, "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ=="],
- "undici": ["undici@7.22.0", "", {}, "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg=="],
+ "undici": ["undici@7.24.4", "", {}, "sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w=="],
- "undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+ "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"unicode-canonical-property-names-ecmascript": ["unicode-canonical-property-names-ecmascript@2.0.1", "", {}, "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg=="],
@@ -5529,6 +5531,8 @@
"@google/genai/google-auth-library": ["google-auth-library@10.5.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="],
+ "@grpc/grpc-js/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"@grpc/proto-loader/protobufjs": ["protobufjs@7.4.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw=="],
"@headlessui/react/@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.12", "", { "dependencies": { "@tanstack/virtual-core": "3.13.12" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA=="],
@@ -5549,16 +5553,30 @@
"@istanbuljs/load-nyc-config/resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
+ "@jest/console/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"@jest/console/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
+ "@jest/core/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"@jest/core/pretty-format": ["pretty-format@30.2.0", "", { "dependencies": { "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" } }, "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA=="],
"@jest/core/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
+ "@jest/environment/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
+ "@jest/environment-jsdom-abstract/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"@jest/expect/expect": ["expect@30.2.0", "", { "dependencies": { "@jest/expect-utils": "30.2.0", "@jest/get-type": "30.1.0", "jest-matcher-utils": "30.2.0", "jest-message-util": "30.2.0", "jest-mock": "30.2.0", "jest-util": "30.2.0" } }, "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw=="],
+ "@jest/fake-timers/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
+ "@jest/pattern/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"@jest/reporters/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
+ "@jest/reporters/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"@jest/reporters/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": "dist/esm/bin.mjs" }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
"@jest/reporters/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
@@ -5571,6 +5589,8 @@
"@jest/transform/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
+ "@jest/types/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"@jridgewell/gen-mapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@jridgewell/remapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
@@ -5597,16 +5617,12 @@
"@langchain/mistralai/uuid": ["uuid@10.0.0", "", { "bin": "dist/bin/uuid" }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
- "@librechat/agents/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
-
- "@librechat/backend/@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.6", "", { "dependencies": { "@smithy/abort-controller": "^4.2.6", "@smithy/protocol-http": "^5.3.6", "@smithy/querystring-builder": "^4.2.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-Gsb9jf4ido5BhPfani4ggyrKDd3ZK+vTFWmUaZeFg5G3E5nhFmqiTzAIbHqmPs1sARuJawDiGMGR/nY+Gw6+aQ=="],
+ "@librechat/client/rollup": ["rollup@4.37.0", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.37.0", "@rollup/rollup-android-arm64": "4.37.0", "@rollup/rollup-darwin-arm64": "4.37.0", "@rollup/rollup-darwin-x64": "4.37.0", "@rollup/rollup-freebsd-arm64": "4.37.0", "@rollup/rollup-freebsd-x64": "4.37.0", "@rollup/rollup-linux-arm-gnueabihf": "4.37.0", "@rollup/rollup-linux-arm-musleabihf": "4.37.0", "@rollup/rollup-linux-arm64-gnu": "4.37.0", "@rollup/rollup-linux-arm64-musl": "4.37.0", "@rollup/rollup-linux-loongarch64-gnu": "4.37.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.37.0", "@rollup/rollup-linux-riscv64-gnu": "4.37.0", "@rollup/rollup-linux-riscv64-musl": "4.37.0", "@rollup/rollup-linux-s390x-gnu": "4.37.0", "@rollup/rollup-linux-x64-gnu": "4.37.0", "@rollup/rollup-linux-x64-musl": "4.37.0", "@rollup/rollup-win32-arm64-msvc": "4.37.0", "@rollup/rollup-win32-ia32-msvc": "4.37.0", "@rollup/rollup-win32-x64-msvc": "4.37.0", "fsevents": "~2.3.2" }, "bin": "dist/bin/rollup" }, "sha512-iAtQy/L4QFU+rTJ1YUjXqJOJzuwEghqWzCEYD2FEghT7Gsy1VdABntrO4CLopA5IkflTyqNiLNwPcOJ3S7UKLg=="],
"@librechat/frontend/@react-spring/web": ["@react-spring/web@9.7.5", "", { "dependencies": { "@react-spring/animated": "~9.7.5", "@react-spring/core": "~9.7.5", "@react-spring/shared": "~9.7.5", "@react-spring/types": "~9.7.5" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "sha512-lmvqGwpe+CSttsWNZVr+Dg62adtKhauGwLyGE/RRyZ8AAMLgb9x3NDMA5RMElXo+IMyTkPp7nxTB8ZQlmhb6JQ=="],
"@librechat/frontend/@testing-library/jest-dom": ["@testing-library/jest-dom@5.17.0", "", { "dependencies": { "@adobe/css-tools": "^4.0.1", "@babel/runtime": "^7.9.2", "@types/testing-library__jest-dom": "^5.9.1", "aria-query": "^5.0.0", "chalk": "^3.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.5.6", "lodash": "^4.17.15", "redent": "^3.0.0" } }, "sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg=="],
- "@librechat/frontend/@types/node": ["@types/node@20.19.37", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw=="],
-
"@librechat/frontend/dompurify": ["dompurify@3.3.0", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ=="],
"@librechat/frontend/framer-motion": ["framer-motion@11.18.2", "", { "dependencies": { "motion-dom": "^11.18.1", "motion-utils": "^11.18.1", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid"] }, "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w=="],
@@ -5629,45 +5645,9 @@
"@node-saml/passport-saml/passport": ["passport@0.7.0", "", { "dependencies": { "passport-strategy": "1.x.x", "pause": "0.0.1", "utils-merge": "^1.0.1" } }, "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ=="],
- "@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.207.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-transformer": "0.207.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-4RQluMVVGMrHok/3SVeSJ6EnRNkA2MINcX88sh+d/7DjGUrewW/WT88IsMEci0wUM+5ykTpPPNbEOoW+jwHnbw=="],
+ "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.208.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-transformer": "0.208.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA=="],
- "@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.207.0", "@opentelemetry/sdk-metrics": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+6DRZLqM02uTIY5GASMZWUwr52sLfNiEe20+OEaZKhztCs3+2LxoTjb6JxFRd9q1qNqckXKYlUKjbH/AhG8/ZA=="],
-
- "@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.207.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-transformer": "0.207.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-4RQluMVVGMrHok/3SVeSJ6EnRNkA2MINcX88sh+d/7DjGUrewW/WT88IsMEci0wUM+5ykTpPPNbEOoW+jwHnbw=="],
-
- "@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.207.0", "@opentelemetry/sdk-metrics": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+6DRZLqM02uTIY5GASMZWUwr52sLfNiEe20+OEaZKhztCs3+2LxoTjb6JxFRd9q1qNqckXKYlUKjbH/AhG8/ZA=="],
-
- "@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.207.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-transformer": "0.207.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-4RQluMVVGMrHok/3SVeSJ6EnRNkA2MINcX88sh+d/7DjGUrewW/WT88IsMEci0wUM+5ykTpPPNbEOoW+jwHnbw=="],
-
- "@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.207.0", "@opentelemetry/sdk-metrics": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+6DRZLqM02uTIY5GASMZWUwr52sLfNiEe20+OEaZKhztCs3+2LxoTjb6JxFRd9q1qNqckXKYlUKjbH/AhG8/ZA=="],
-
- "@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.207.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-transformer": "0.207.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-4RQluMVVGMrHok/3SVeSJ6EnRNkA2MINcX88sh+d/7DjGUrewW/WT88IsMEci0wUM+5ykTpPPNbEOoW+jwHnbw=="],
-
- "@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.207.0", "@opentelemetry/sdk-metrics": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+6DRZLqM02uTIY5GASMZWUwr52sLfNiEe20+OEaZKhztCs3+2LxoTjb6JxFRd9q1qNqckXKYlUKjbH/AhG8/ZA=="],
-
- "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.207.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-transformer": "0.207.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-4RQluMVVGMrHok/3SVeSJ6EnRNkA2MINcX88sh+d/7DjGUrewW/WT88IsMEci0wUM+5ykTpPPNbEOoW+jwHnbw=="],
-
- "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.207.0", "@opentelemetry/sdk-metrics": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+6DRZLqM02uTIY5GASMZWUwr52sLfNiEe20+OEaZKhztCs3+2LxoTjb6JxFRd9q1qNqckXKYlUKjbH/AhG8/ZA=="],
-
- "@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.207.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-transformer": "0.207.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-4RQluMVVGMrHok/3SVeSJ6EnRNkA2MINcX88sh+d/7DjGUrewW/WT88IsMEci0wUM+5ykTpPPNbEOoW+jwHnbw=="],
-
- "@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.207.0", "@opentelemetry/sdk-metrics": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+6DRZLqM02uTIY5GASMZWUwr52sLfNiEe20+OEaZKhztCs3+2LxoTjb6JxFRd9q1qNqckXKYlUKjbH/AhG8/ZA=="],
-
- "@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.207.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-transformer": "0.207.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-4RQluMVVGMrHok/3SVeSJ6EnRNkA2MINcX88sh+d/7DjGUrewW/WT88IsMEci0wUM+5ykTpPPNbEOoW+jwHnbw=="],
-
- "@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.207.0", "@opentelemetry/sdk-metrics": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+6DRZLqM02uTIY5GASMZWUwr52sLfNiEe20+OEaZKhztCs3+2LxoTjb6JxFRd9q1qNqckXKYlUKjbH/AhG8/ZA=="],
-
- "@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.207.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-transformer": "0.207.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-4RQluMVVGMrHok/3SVeSJ6EnRNkA2MINcX88sh+d/7DjGUrewW/WT88IsMEci0wUM+5ykTpPPNbEOoW+jwHnbw=="],
-
- "@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.207.0", "@opentelemetry/sdk-metrics": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+6DRZLqM02uTIY5GASMZWUwr52sLfNiEe20+OEaZKhztCs3+2LxoTjb6JxFRd9q1qNqckXKYlUKjbH/AhG8/ZA=="],
-
- "@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.207.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-transformer": "0.207.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-4RQluMVVGMrHok/3SVeSJ6EnRNkA2MINcX88sh+d/7DjGUrewW/WT88IsMEci0wUM+5ykTpPPNbEOoW+jwHnbw=="],
-
- "@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.207.0", "@opentelemetry/sdk-metrics": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+6DRZLqM02uTIY5GASMZWUwr52sLfNiEe20+OEaZKhztCs3+2LxoTjb6JxFRd9q1qNqckXKYlUKjbH/AhG8/ZA=="],
-
- "@opentelemetry/otlp-transformer/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.208.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg=="],
-
- "@opentelemetry/otlp-transformer/@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.208.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.208.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA=="],
+ "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.208.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.208.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.208.0", "@opentelemetry/sdk-metrics": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ=="],
"@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.4.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw=="],
@@ -5931,13 +5911,33 @@
"@testing-library/dom/pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
+ "@types/body-parser/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
+ "@types/connect/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
+ "@types/express-serve-static-core/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
+ "@types/jsdom/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
+ "@types/jsonwebtoken/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
+ "@types/ldapjs/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"@types/mdast/@types/unist": ["@types/unist@2.0.10", "", {}, "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA=="],
+ "@types/node-fetch/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
+ "@types/send/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
+ "@types/serve-static/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"@types/testing-library__jest-dom/@types/jest": ["@types/jest@29.5.12", "", { "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" } }, "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw=="],
"@types/winston/winston": ["winston@3.11.0", "", { "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", "async": "^3.2.3", "is-stream": "^2.0.0", "logform": "^2.4.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", "winston-transport": "^4.5.0" } }, "sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g=="],
- "@types/ws/@types/node": ["@types/node@20.19.37", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw=="],
+ "@types/xml-encryption/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
+ "@types/xml2js/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
"@typescript-eslint/parser/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
@@ -5981,6 +5981,8 @@
"browserify-sign/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
+ "bun-types/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"caniuse-api/browserslist": ["browserslist@4.28.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", "electron-to-chromium": "^1.5.249", "node-releases": "^2.0.27", "update-browserslist-db": "^1.1.4" }, "bin": "cli.js" }, "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ=="],
"chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
@@ -6087,8 +6089,6 @@
"google-auth-library/gcp-metadata": ["gcp-metadata@6.1.1", "", { "dependencies": { "gaxios": "^6.1.1", "google-logging-utils": "^0.0.2", "json-bigint": "^1.0.0" } }, "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A=="],
- "happy-dom/@types/node": ["@types/node@20.19.37", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw=="],
-
"happy-dom/whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="],
"happy-dom/ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
@@ -6151,6 +6151,8 @@
"jest-changed-files/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
+ "jest-circus/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"jest-circus/jest-matcher-utils": ["jest-matcher-utils@30.2.0", "", { "dependencies": { "@jest/get-type": "30.1.0", "chalk": "^4.1.2", "jest-diff": "30.2.0", "pretty-format": "30.2.0" } }, "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg=="],
"jest-circus/pretty-format": ["pretty-format@30.2.0", "", { "dependencies": { "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" } }, "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA=="],
@@ -6167,8 +6169,14 @@
"jest-each/pretty-format": ["pretty-format@30.2.0", "", { "dependencies": { "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" } }, "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA=="],
+ "jest-environment-jsdom/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
+ "jest-environment-node/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"jest-file-loader/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
+ "jest-haste-map/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"jest-leak-detector/pretty-format": ["pretty-format@30.2.0", "", { "dependencies": { "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" } }, "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA=="],
"jest-matcher-utils/jest-diff": ["jest-diff@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw=="],
@@ -6177,10 +6185,16 @@
"jest-message-util/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
+ "jest-mock/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"jest-resolve/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
+ "jest-runner/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"jest-runner/source-map-support": ["source-map-support@0.5.13", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w=="],
+ "jest-runtime/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"jest-runtime/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": "dist/esm/bin.mjs" }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
"jest-runtime/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
@@ -6199,8 +6213,14 @@
"jest-snapshot/synckit": ["synckit@0.11.11", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw=="],
+ "jest-util/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"jest-validate/pretty-format": ["pretty-format@30.2.0", "", { "dependencies": { "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" } }, "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA=="],
+ "jest-watcher/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
+ "jest-worker/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
"js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
@@ -6233,8 +6253,6 @@
"ldapauth-fork/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
- "librechat-data-provider/@types/node": ["@types/node@20.19.37", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw=="],
-
"lint-staged/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
"lint-staged/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
@@ -6259,8 +6277,6 @@
"memorystore/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
- "mermaid/dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="],
-
"mermaid/marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="],
"mermaid/uuid": ["uuid@11.1.0", "", { "bin": "dist/esm/bin/uuid" }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
@@ -6317,6 +6333,8 @@
"playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
+ "postcss/nanoid": ["nanoid@3.3.8", "", { "bin": "bin/nanoid.cjs" }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="],
+
"postcss-attribute-case-insensitive/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="],
"postcss-colormin/browserslist": ["browserslist@4.28.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", "electron-to-chromium": "^1.5.249", "node-releases": "^2.0.27", "update-browserslist-db": "^1.1.4" }, "bin": "cli.js" }, "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ=="],
@@ -6365,8 +6383,6 @@
"prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
- "protobufjs/@types/node": ["@types/node@20.19.37", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw=="],
-
"public-encrypt/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="],
"rc-util/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
@@ -6399,6 +6415,8 @@
"restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
+ "rollup/@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
+
"rollup-plugin-postcss/resolve": ["resolve@1.22.8", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw=="],
"rollup-plugin-typescript2/@rollup/pluginutils": ["@rollup/pluginutils@4.2.1", "", { "dependencies": { "estree-walker": "^2.0.1", "picomatch": "^2.2.2" } }, "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ=="],
@@ -6495,8 +6513,6 @@
"vite/postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
- "vite/rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
-
"winston-daily-rotate-file/winston-transport": ["winston-transport@4.7.0", "", { "dependencies": { "logform": "^2.3.2", "readable-stream": "^3.6.0", "triple-beam": "^1.3.0" } }, "sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg=="],
"workbox-build/@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
@@ -6863,6 +6879,10 @@
"@google/genai/google-auth-library/gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="],
+ "@grpc/grpc-js/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "@grpc/proto-loader/protobufjs/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"@headlessui/react/@tanstack/react-virtual/@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.12", "", {}, "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA=="],
"@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
@@ -6871,18 +6891,34 @@
"@istanbuljs/load-nyc-config/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
+ "@jest/console/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "@jest/core/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
"@jest/core/pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
+ "@jest/environment-jsdom-abstract/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "@jest/environment/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
"@jest/expect/expect/@jest/expect-utils": ["@jest/expect-utils@30.2.0", "", { "dependencies": { "@jest/get-type": "30.1.0" } }, "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA=="],
"@jest/expect/expect/jest-matcher-utils": ["jest-matcher-utils@30.2.0", "", { "dependencies": { "@jest/get-type": "30.1.0", "chalk": "^4.1.2", "jest-diff": "30.2.0", "pretty-format": "30.2.0" } }, "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg=="],
+ "@jest/fake-timers/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "@jest/pattern/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "@jest/reporters/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
"@jest/reporters/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@jest/reporters/glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
"@jest/reporters/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
+ "@jest/types/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
"@langchain/aws/@aws-sdk/client-bedrock-runtime/@aws-sdk/core": ["@aws-sdk/core@3.927.0", "", { "dependencies": { "@aws-sdk/types": "3.922.0", "@aws-sdk/xml-builder": "3.921.0", "@smithy/core": "^3.17.2", "@smithy/node-config-provider": "^4.3.4", "@smithy/property-provider": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/signature-v4": "^5.3.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-QOtR9QdjNeC7bId3fc/6MnqoEezvQ2Fk+x6F+Auf7NhOxwYAtB1nvh0k3+gJHWVGpfxN1I8keahRZd79U68/ag=="],
"@langchain/aws/@aws-sdk/client-bedrock-runtime/@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.922.0", "", { "dependencies": { "@aws-sdk/types": "3.922.0", "@smithy/eventstream-codec": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-DTKHeH1Bk17zSdoa5qXPGwCmZXuhQReqXOVW2/jIVX8NGVvnraH7WppGPlQxBjFtwSSwVTgzH2NVPgediQphNA=="],
@@ -6999,13 +7035,41 @@
"@langchain/google-gauth/google-auth-library/gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="],
- "@librechat/backend/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-P7JD4J+wxHMpGxqIg6SHno2tPkZbBUBLbPpR5/T1DEUvw/mEaINBMaPFZNM7lA+ToSCZ36j6nMHa+5kej+fhGg=="],
+ "@librechat/client/rollup/@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.37.0", "", { "os": "android", "cpu": "arm" }, "sha512-l7StVw6WAa8l3vA1ov80jyetOAEo1FtHvZDbzXDO/02Sq/QVvqlHkYoFwDJPIMj0GKiistsBudfx5tGFnwYWDQ=="],
- "@librechat/backend/@smithy/node-http-handler/@smithy/protocol-http": ["@smithy/protocol-http@5.3.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-qLRZzP2+PqhE3OSwvY2jpBbP0WKTZ9opTsn+6IWYI0SKVpbG+imcfNxXPq9fj5XeaUTr7odpsNpK6dmoiM1gJQ=="],
+ "@librechat/client/rollup/@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.37.0", "", { "os": "android", "cpu": "arm64" }, "sha512-6U3SlVyMxezt8Y+/iEBcbp945uZjJwjZimu76xoG7tO1av9VO691z8PkhzQ85ith2I8R2RddEPeSfcbyPfD4hA=="],
- "@librechat/backend/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-MeM9fTAiD3HvoInK/aA8mgJaKQDvm8N0dKy6EiFaCfgpovQr4CaOkJC28XqlSRABM+sHdSQXbC8NZ0DShBMHqg=="],
+ "@librechat/client/rollup/@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.37.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+iTQ5YHuGmPt10NTzEyMPbayiNTcOZDWsbxZYR1ZnmLnZxG17ivrPSWFO9j6GalY0+gV3Jtwrrs12DBscxnlYA=="],
- "@librechat/backend/@smithy/node-http-handler/@smithy/types": ["@smithy/types@4.10.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-K9mY7V/f3Ul+/Gz4LJANZ3vJ/yiBIwCyxe0sPT4vNJK63Srvd+Yk1IzP0t+nE7XFSpIGtzR71yljtnqpUTYFlQ=="],
+ "@librechat/client/rollup/@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.37.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-m8W2UbxLDcmRKVjgl5J/k4B8d7qX2EcJve3Sut7YGrQoPtCIQGPH5AMzuFvYRWZi0FVS0zEY4c8uttPfX6bwYQ=="],
+
+ "@librechat/client/rollup/@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.37.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-FOMXGmH15OmtQWEt174v9P1JqqhlgYge/bUjIbiVD1nI1NeJ30HYT9SJlZMqdo1uQFyt9cz748F1BHghWaDnVA=="],
+
+ "@librechat/client/rollup/@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.37.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-SZMxNttjPKvV14Hjck5t70xS3l63sbVwl98g3FlVVx2YIDmfUIy29jQrsw06ewEYQ8lQSuY9mpAPlmgRD2iSsA=="],
+
+ "@librechat/client/rollup/@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.37.0", "", { "os": "linux", "cpu": "arm" }, "sha512-hhAALKJPidCwZcj+g+iN+38SIOkhK2a9bqtJR+EtyxrKKSt1ynCBeqrQy31z0oWU6thRZzdx53hVgEbRkuI19w=="],
+
+ "@librechat/client/rollup/@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.37.0", "", { "os": "linux", "cpu": "arm" }, "sha512-jUb/kmn/Gd8epbHKEqkRAxq5c2EwRt0DqhSGWjPFxLeFvldFdHQs/n8lQ9x85oAeVb6bHcS8irhTJX2FCOd8Ag=="],
+
+ "@librechat/client/rollup/@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.37.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-oNrJxcQT9IcbcmKlkF+Yz2tmOxZgG9D9GRq+1OE6XCQwCVwxixYAa38Z8qqPzQvzt1FCfmrHX03E0pWoXm1DqA=="],
+
+ "@librechat/client/rollup/@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.37.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-pfxLBMls+28Ey2enpX3JvjEjaJMBX5XlPCZNGxj4kdJyHduPBXtxYeb8alo0a7bqOoWZW2uKynhHxF/MWoHaGQ=="],
+
+ "@librechat/client/rollup/@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.37.0", "", { "os": "linux", "cpu": "none" }, "sha512-PpWwHMPCVpFZLTfLq7EWJWvrmEuLdGn1GMYcm5MV7PaRgwCEYJAwiN94uBuZev0/J/hFIIJCsYw4nLmXA9J7Pw=="],
+
+ "@librechat/client/rollup/@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.37.0", "", { "os": "linux", "cpu": "none" }, "sha512-DTNwl6a3CfhGTAOYZ4KtYbdS8b+275LSLqJVJIrPa5/JuIufWWZ/QFvkxp52gpmguN95eujrM68ZG+zVxa8zHA=="],
+
+ "@librechat/client/rollup/@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.37.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-hZDDU5fgWvDdHFuExN1gBOhCuzo/8TMpidfOR+1cPZJflcEzXdCy1LjnklQdW8/Et9sryOPJAKAQRw8Jq7Tg+A=="],
+
+ "@librechat/client/rollup/@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.37.0", "", { "os": "linux", "cpu": "x64" }, "sha512-pKivGpgJM5g8dwj0ywBwe/HeVAUSuVVJhUTa/URXjxvoyTT/AxsLTAbkHkDHG7qQxLoW2s3apEIl26uUe08LVQ=="],
+
+ "@librechat/client/rollup/@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.37.0", "", { "os": "linux", "cpu": "x64" }, "sha512-E2lPrLKE8sQbY/2bEkVTGDEk4/49UYRVWgj90MY8yPjpnGBQ+Xi1Qnr7b7UIWw1NOggdFQFOLZ8+5CzCiz143w=="],
+
+ "@librechat/client/rollup/@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.37.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Jm7biMazjNzTU4PrQtr7VS8ibeys9Pn29/1bm4ph7CP2kf21950LgN+BaE2mJ1QujnvOc6p54eWWiVvn05SOBg=="],
+
+ "@librechat/client/rollup/@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.37.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-e3/1SFm1OjefWICB2Ucstg2dxYDkDTZGDYgwufcbsxTHyqQps1UQf33dFEChBNmeSsTOyrjw2JJq0zbG5GF6RA=="],
+
+ "@librechat/client/rollup/@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.37.0", "", { "os": "win32", "cpu": "x64" }, "sha512-LWbXUBwn/bcLx2sSsqy7pK5o+Nr+VCoRoAohfJ5C/aBio9nfJmGQqHAhU6pwxV/RmyTk5AqdySma7uwWGlmeuA=="],
"@librechat/frontend/@react-spring/web/@react-spring/animated": ["@react-spring/animated@9.7.5", "", { "dependencies": { "@react-spring/shared": "~9.7.5", "@react-spring/types": "~9.7.5" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "sha512-Tqrwz7pIlsSDITzxoLS3n/v/YCUHQdOIKtOJf4yL6kYVSDTSmVK1LI1Q3M/uu2Sx4X3pIWF3xLUhlsA6SPNTNg=="],
@@ -7025,8 +7089,6 @@
"@librechat/frontend/@testing-library/jest-dom/lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
- "@librechat/frontend/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
-
"@librechat/frontend/framer-motion/motion-dom": ["motion-dom@11.18.1", "", { "dependencies": { "motion-utils": "^11.18.1" } }, "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw=="],
"@librechat/frontend/framer-motion/motion-utils": ["motion-utils@11.18.1", "", {}, "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA=="],
@@ -7045,27 +7107,13 @@
"@node-saml/passport-saml/@types/express/@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="],
- "@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.4.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw=="],
+ "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.208.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg=="],
- "@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.4.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw=="],
+ "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.208.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.208.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA=="],
- "@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.4.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw=="],
+ "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.4.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw=="],
- "@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.4.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw=="],
-
- "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.4.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw=="],
-
- "@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.4.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw=="],
-
- "@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.4.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw=="],
-
- "@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.4.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw=="],
-
- "@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.4.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw=="],
-
- "@opentelemetry/sdk-node/@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.207.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-transformer": "0.207.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-4RQluMVVGMrHok/3SVeSJ6EnRNkA2MINcX88sh+d/7DjGUrewW/WT88IsMEci0wUM+5ykTpPPNbEOoW+jwHnbw=="],
-
- "@opentelemetry/sdk-node/@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.207.0", "@opentelemetry/sdk-metrics": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+6DRZLqM02uTIY5GASMZWUwr52sLfNiEe20+OEaZKhztCs3+2LxoTjb6JxFRd9q1qNqckXKYlUKjbH/AhG8/ZA=="],
+ "@opentelemetry/otlp-transformer/protobufjs/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
"@radix-ui/react-arrow/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.0.2", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-compose-refs": "1.0.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg=="],
@@ -7171,11 +7219,31 @@
"@smithy/credential-provider-imds/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@3.0.3", "", { "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" } }, "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ=="],
+ "@types/body-parser/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "@types/connect/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "@types/express-serve-static-core/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "@types/jsdom/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "@types/jsonwebtoken/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "@types/ldapjs/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "@types/node-fetch/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "@types/send/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "@types/serve-static/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
"@types/winston/winston/logform": ["logform@2.6.0", "", { "dependencies": { "@colors/colors": "1.6.0", "@types/triple-beam": "^1.3.2", "fecha": "^4.2.0", "ms": "^2.1.1", "safe-stable-stringify": "^2.3.1", "triple-beam": "^1.3.0" } }, "sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ=="],
"@types/winston/winston/winston-transport": ["winston-transport@4.7.0", "", { "dependencies": { "logform": "^2.3.2", "readable-stream": "^3.6.0", "triple-beam": "^1.3.0" } }, "sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg=="],
- "@types/ws/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
+ "@types/xml-encryption/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "@types/xml2js/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
@@ -7205,6 +7273,8 @@
"browserify-sign/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
+ "bun-types/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
"caniuse-api/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.8.28", "", { "bin": "dist/cli.js" }, "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ=="],
"caniuse-api/browserslist/update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": "cli.js" }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="],
@@ -7243,6 +7313,8 @@
"expect/jest-util/@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="],
+ "expect/jest-util/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"expect/jest-util/ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="],
"expect/jest-util/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
@@ -7265,8 +7337,6 @@
"google-auth-library/gcp-metadata/google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="],
- "happy-dom/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
-
"hast-util-from-html-isomorphic/@types/hast/@types/unist": ["@types/unist@2.0.10", "", {}, "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA=="],
"hast-util-from-html/@types/hast/@types/unist": ["@types/unist@2.0.10", "", {}, "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA=="],
@@ -7303,6 +7373,8 @@
"jest-changed-files/execa/strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
+ "jest-circus/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
"jest-circus/pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
"jest-config/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
@@ -7317,10 +7389,22 @@
"jest-each/pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
+ "jest-environment-jsdom/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "jest-environment-node/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "jest-haste-map/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
"jest-leak-detector/pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
"jest-message-util/pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
+ "jest-mock/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "jest-runner/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "jest-runtime/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
"jest-runtime/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"jest-runtime/glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
@@ -7331,8 +7415,14 @@
"jest-snapshot/synckit/@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="],
+ "jest-util/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
"jest-validate/pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
+ "jest-watcher/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "jest-worker/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
"jest-worker/supports-color/has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"jsdom/whatwg-url/tr46": ["tr46@5.1.1", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw=="],
@@ -7345,8 +7435,6 @@
"jwks-rsa/@types/express/@types/express-serve-static-core": ["@types/express-serve-static-core@4.19.6", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A=="],
- "librechat-data-provider/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
-
"log-update/slice-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
"log-update/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.0.0", "", { "dependencies": { "get-east-asian-width": "^1.0.0" } }, "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA=="],
@@ -7401,8 +7489,6 @@
"pretty-format/@jest/schemas/@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="],
- "protobufjs/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
-
"rehype-highlight/@types/hast/@types/unist": ["@types/unist@2.0.10", "", {}, "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA=="],
"rehype-highlight/unified/@types/unist": ["@types/unist@2.0.10", "", {}, "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA=="],
@@ -7457,46 +7543,6 @@
"vfile-location/vfile/vfile-message": ["vfile-message@3.1.4", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-stringify-position": "^3.0.0" } }, "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw=="],
- "vite/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
-
- "vite/rollup/@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="],
-
- "vite/rollup/@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="],
-
- "vite/rollup/@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg=="],
-
- "vite/rollup/@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.59.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w=="],
-
- "vite/rollup/@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.59.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA=="],
-
- "vite/rollup/@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.59.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg=="],
-
- "vite/rollup/@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw=="],
-
- "vite/rollup/@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA=="],
-
- "vite/rollup/@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA=="],
-
- "vite/rollup/@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA=="],
-
- "vite/rollup/@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg=="],
-
- "vite/rollup/@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg=="],
-
- "vite/rollup/@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.59.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w=="],
-
- "vite/rollup/@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg=="],
-
- "vite/rollup/@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg=="],
-
- "vite/rollup/@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.59.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A=="],
-
- "vite/rollup/@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.59.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA=="],
-
- "vite/rollup/@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="],
-
- "vite/rollup/@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
-
"winston-daily-rotate-file/winston-transport/logform": ["logform@2.6.0", "", { "dependencies": { "@colors/colors": "1.6.0", "@types/triple-beam": "^1.3.2", "fecha": "^4.2.0", "ms": "^2.1.1", "safe-stable-stringify": "^2.3.1", "triple-beam": "^1.3.0" } }, "sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ=="],
"workbox-build/@babel/core/@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
@@ -7813,6 +7859,8 @@
"@google/genai/google-auth-library/gaxios/rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": "dist/esm/bin.mjs" }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="],
+ "@grpc/proto-loader/protobufjs/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
"@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
"@jest/expect/expect/jest-matcher-utils/pretty-format": ["pretty-format@30.2.0", "", { "dependencies": { "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" } }, "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA=="],
@@ -7923,8 +7971,6 @@
"@langchain/google-gauth/google-auth-library/gaxios/rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": "dist/esm/bin.mjs" }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="],
- "@librechat/backend/@smithy/node-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="],
-
"@librechat/frontend/@react-spring/web/@react-spring/shared/@react-spring/rafz": ["@react-spring/rafz@9.7.5", "", {}, "sha512-5ZenDQMC48wjUzPAm1EtwQ5Ot3bLIAwwqP2w2owG5KoNdNHpEJV263nGhCeKKmuA3vG2zLLOdu3or6kuDjA6Aw=="],
"@librechat/frontend/@testing-library/jest-dom/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
@@ -7937,9 +7983,13 @@
"@mcp-ui/client/@modelcontextprotocol/sdk/express/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
+ "@node-saml/passport-saml/@types/express/@types/express-serve-static-core/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"@node-saml/passport-saml/@types/express/@types/express-serve-static-core/@types/qs": ["@types/qs@6.9.17", "", {}, "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ=="],
- "@opentelemetry/sdk-node/@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.4.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw=="],
+ "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer/protobufjs/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
+ "@opentelemetry/otlp-transformer/protobufjs/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"@radix-ui/react-arrow/@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw=="],
@@ -7971,8 +8021,12 @@
"expect/jest-message-util/@jest/types/@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
+ "expect/jest-message-util/@jest/types/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"expect/jest-util/@jest/types/@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
+ "expect/jest-util/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
"express-static-gzip/serve-static/send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"express-static-gzip/serve-static/send/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
@@ -7999,6 +8053,8 @@
"jsdom/whatwg-url/tr46/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
+ "jwks-rsa/@types/express/@types/express-serve-static-core/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="],
+
"mongodb-connection-string-url/whatwg-url/tr46/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"multer/type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
@@ -8485,6 +8541,10 @@
"@librechat/frontend/@testing-library/jest-dom/chalk/supports-color/has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
+ "@node-saml/passport-saml/@types/express/@types/express-serve-static-core/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer/protobufjs/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
"@vitejs/plugin-react/@babel/core/@babel/helper-compilation-targets/browserslist/caniuse-lite": ["caniuse-lite@1.0.30001777", "", {}, "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ=="],
"@vitejs/plugin-react/@babel/core/@babel/helper-compilation-targets/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.307", "", {}, "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg=="],
@@ -8495,10 +8555,14 @@
"expect/jest-message-util/@jest/types/@jest/schemas/@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="],
+ "expect/jest-message-util/@jest/types/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
"expect/jest-util/@jest/types/@jest/schemas/@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="],
"express-static-gzip/serve-static/send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+ "jwks-rsa/@types/express/@types/express-serve-static-core/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
"pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
"svgo/css-select/domutils/dom-serializer/entities": ["entities@2.2.0", "", {}, "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="],
diff --git a/client/jest.config.cjs b/client/jest.config.cjs
index 1c698d08a3..f97adb39ce 100644
--- a/client/jest.config.cjs
+++ b/client/jest.config.cjs
@@ -1,4 +1,4 @@
-/** v0.8.3 */
+/** v0.8.4-rc1 */
module.exports = {
roots: ['/src'],
testEnvironment: 'jsdom',
diff --git a/client/package.json b/client/package.json
index 250afc9990..a3ff5529e5 100644
--- a/client/package.json
+++ b/client/package.json
@@ -1,6 +1,6 @@
{
"name": "@librechat/frontend",
- "version": "v0.8.3",
+ "version": "v0.8.4-rc1",
"description": "",
"type": "module",
"scripts": {
diff --git a/e2e/jestSetup.js b/e2e/jestSetup.js
index 64c1a8546f..f6d5bf4c66 100644
--- a/e2e/jestSetup.js
+++ b/e2e/jestSetup.js
@@ -1,3 +1,3 @@
-// v0.8.3
+// v0.8.4-rc1
// See .env.test.example for an example of the '.env.test' file.
require('dotenv').config({ path: './e2e/.env.test' });
diff --git a/helm/librechat/Chart.yaml b/helm/librechat/Chart.yaml
index a2dff261c7..d39ec8811c 100755
--- a/helm/librechat/Chart.yaml
+++ b/helm/librechat/Chart.yaml
@@ -15,7 +15,7 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
-version: 2.0.0
+version: 2.0.1
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
@@ -23,7 +23,7 @@ version: 2.0.0
# It is recommended to use it with quotes.
# renovate: image=registry.librechat.ai/danny-avila/librechat
-appVersion: "v0.8.3"
+appVersion: "v0.8.4-rc1"
home: https://www.librechat.ai
diff --git a/package-lock.json b/package-lock.json
index 45f737ad8f..f39ae2d180 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "LibreChat",
- "version": "v0.8.3",
+ "version": "v0.8.4-rc1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "LibreChat",
- "version": "v0.8.3",
+ "version": "v0.8.4-rc1",
"license": "ISC",
"workspaces": [
"api",
@@ -46,7 +46,7 @@
},
"api": {
"name": "@librechat/backend",
- "version": "v0.8.3",
+ "version": "v0.8.4-rc1",
"license": "ISC",
"dependencies": {
"@anthropic-ai/vertex-sdk": "^0.14.3",
@@ -429,7 +429,7 @@
},
"client": {
"name": "@librechat/frontend",
- "version": "v0.8.3",
+ "version": "v0.8.4-rc1",
"license": "ISC",
"dependencies": {
"@ariakit/react": "^0.4.15",
@@ -44195,7 +44195,7 @@
},
"packages/api": {
"name": "@librechat/api",
- "version": "1.7.25",
+ "version": "1.7.26",
"license": "ISC",
"devDependencies": {
"@babel/preset-env": "^7.21.5",
@@ -44311,7 +44311,7 @@
},
"packages/client": {
"name": "@librechat/client",
- "version": "0.4.54",
+ "version": "0.4.55",
"devDependencies": {
"@babel/core": "^7.28.5",
"@babel/preset-env": "^7.28.5",
@@ -46135,7 +46135,7 @@
},
"packages/data-provider": {
"name": "librechat-data-provider",
- "version": "0.8.302",
+ "version": "0.8.400",
"license": "ISC",
"dependencies": {
"axios": "^1.13.5",
@@ -46193,7 +46193,7 @@
},
"packages/data-schemas": {
"name": "@librechat/data-schemas",
- "version": "0.0.38",
+ "version": "0.0.39",
"license": "MIT",
"devDependencies": {
"@rollup/plugin-alias": "^5.1.0",
diff --git a/package.json b/package.json
index ecbede482e..6605752f39 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "LibreChat",
- "version": "v0.8.3",
+ "version": "v0.8.4-rc1",
"description": "",
"packageManager": "npm@11.10.0",
"workspaces": [
diff --git a/packages/api/package.json b/packages/api/package.json
index b3b40c79a2..46fbeb02b6 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -1,6 +1,6 @@
{
"name": "@librechat/api",
- "version": "1.7.25",
+ "version": "1.7.26",
"type": "commonjs",
"description": "MCP services for LibreChat",
"main": "dist/index.js",
diff --git a/packages/client/package.json b/packages/client/package.json
index 13d1a4a8cc..e76c1d075a 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -1,6 +1,6 @@
{
"name": "@librechat/client",
- "version": "0.4.54",
+ "version": "0.4.55",
"description": "React components for LibreChat",
"repository": {
"type": "git",
diff --git a/packages/data-provider/package.json b/packages/data-provider/package.json
index a707aef448..09e13b31a7 100644
--- a/packages/data-provider/package.json
+++ b/packages/data-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "librechat-data-provider",
- "version": "0.8.302",
+ "version": "0.8.400",
"description": "data services for librechat apps",
"main": "dist/index.js",
"module": "dist/index.es.js",
diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts
index bb0c180209..e19c69e799 100644
--- a/packages/data-provider/src/config.ts
+++ b/packages/data-provider/src/config.ts
@@ -1740,7 +1740,7 @@ export enum TTSProviders {
/** Enum for app-wide constants */
export enum Constants {
/** Key for the app's version. */
- VERSION = 'v0.8.3',
+ VERSION = 'v0.8.4-rc1',
/** Key for the Custom Config's version (librechat.yaml). */
CONFIG_VERSION = '1.3.6',
/** Standard value for the first message's `parentMessageId` value, to indicate no parent exists. */
diff --git a/packages/data-schemas/package.json b/packages/data-schemas/package.json
index e91bfb8886..87acd16f1e 100644
--- a/packages/data-schemas/package.json
+++ b/packages/data-schemas/package.json
@@ -1,6 +1,6 @@
{
"name": "@librechat/data-schemas",
- "version": "0.0.38",
+ "version": "0.0.39",
"description": "Mongoose schemas and models for LibreChat",
"type": "module",
"main": "dist/index.cjs",
From b5a55b23a4b2f5f6428679b1ee358f18b02d51d8 Mon Sep 17 00:00:00 2001
From: Danny Avila
Date: Tue, 17 Mar 2026 17:04:18 -0400
Subject: [PATCH 02/98] =?UTF-8?q?=F0=9F=93=A6=20chore:=20NPM=20audit=20pac?=
=?UTF-8?q?kages=20(#12286)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* 🔧 chore: Update dependencies in package-lock.json and package.json
- Bump @aws-sdk/client-bedrock-runtime from 3.980.0 to 3.1011.0 and update related dependencies.
- Update fast-xml-parser version from 5.3.8 to 5.5.6 in package.json.
- Adjust various @aws-sdk and @smithy packages to their latest versions for improved functionality and security.
* 🔧 chore: Update @librechat/agents dependency to version 3.1.57 in package.json and package-lock.json
- Bump @librechat/agents from 3.1.56 to 3.1.57 across multiple package files for consistency.
- Remove axios dependency from package.json as it is no longer needed.
---
api/package.json | 2 +-
package-lock.json | 1998 +++++++++++++++----------------------
package.json | 7 +-
packages/api/package.json | 2 +-
4 files changed, 800 insertions(+), 1209 deletions(-)
diff --git a/api/package.json b/api/package.json
index f32de5e778..2255679dae 100644
--- a/api/package.json
+++ b/api/package.json
@@ -44,7 +44,7 @@
"@google/genai": "^1.19.0",
"@keyv/redis": "^4.3.3",
"@langchain/core": "^0.3.80",
- "@librechat/agents": "^3.1.56",
+ "@librechat/agents": "^3.1.57",
"@librechat/api": "*",
"@librechat/data-schemas": "*",
"@microsoft/microsoft-graph-client": "^3.0.7",
diff --git a/package-lock.json b/package-lock.json
index f39ae2d180..2454745a79 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -59,7 +59,7 @@
"@google/genai": "^1.19.0",
"@keyv/redis": "^4.3.3",
"@langchain/core": "^0.3.80",
- "@librechat/agents": "^3.1.56",
+ "@librechat/agents": "^3.1.57",
"@librechat/api": "*",
"@librechat/data-schemas": "*",
"@microsoft/microsoft-graph-client": "^3.0.7",
@@ -3199,57 +3199,73 @@
}
},
"node_modules/@aws-sdk/client-bedrock-runtime": {
- "version": "3.980.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.980.0.tgz",
- "integrity": "sha512-agRy8K543Q4WxCiup12JiSe4rO2gkw4wykaGXD+MEmzG2Nq4ODvKrNHT+XYCyTvk9ehJim/vpu+Stae3nEI0yw==",
+ "version": "3.1011.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1011.0.tgz",
+ "integrity": "sha512-yn5oRLLP1TsGLZqlnyqBjAVmiexYR8/rPG8D+rI5f5+UIvb3zHOmHLXA1m41H/sKXI4embmXfUjvArmjTmfsIw==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "^3.973.5",
- "@aws-sdk/credential-provider-node": "^3.972.4",
- "@aws-sdk/eventstream-handler-node": "^3.972.3",
- "@aws-sdk/middleware-eventstream": "^3.972.3",
- "@aws-sdk/middleware-host-header": "^3.972.3",
- "@aws-sdk/middleware-logger": "^3.972.3",
- "@aws-sdk/middleware-recursion-detection": "^3.972.3",
- "@aws-sdk/middleware-user-agent": "^3.972.5",
- "@aws-sdk/middleware-websocket": "^3.972.3",
- "@aws-sdk/region-config-resolver": "^3.972.3",
- "@aws-sdk/token-providers": "3.980.0",
- "@aws-sdk/types": "^3.973.1",
- "@aws-sdk/util-endpoints": "3.980.0",
- "@aws-sdk/util-user-agent-browser": "^3.972.3",
- "@aws-sdk/util-user-agent-node": "^3.972.3",
- "@smithy/config-resolver": "^4.4.6",
- "@smithy/core": "^3.22.0",
- "@smithy/eventstream-serde-browser": "^4.2.8",
- "@smithy/eventstream-serde-config-resolver": "^4.3.8",
- "@smithy/eventstream-serde-node": "^4.2.8",
- "@smithy/fetch-http-handler": "^5.3.9",
- "@smithy/hash-node": "^4.2.8",
- "@smithy/invalid-dependency": "^4.2.8",
- "@smithy/middleware-content-length": "^4.2.8",
- "@smithy/middleware-endpoint": "^4.4.12",
- "@smithy/middleware-retry": "^4.4.29",
- "@smithy/middleware-serde": "^4.2.9",
- "@smithy/middleware-stack": "^4.2.8",
- "@smithy/node-config-provider": "^4.3.8",
- "@smithy/node-http-handler": "^4.4.8",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/smithy-client": "^4.11.1",
- "@smithy/types": "^4.12.0",
- "@smithy/url-parser": "^4.2.8",
- "@smithy/util-base64": "^4.3.0",
- "@smithy/util-body-length-browser": "^4.2.0",
- "@smithy/util-body-length-node": "^4.2.1",
- "@smithy/util-defaults-mode-browser": "^4.3.28",
- "@smithy/util-defaults-mode-node": "^4.2.31",
- "@smithy/util-endpoints": "^3.2.8",
- "@smithy/util-middleware": "^4.2.8",
- "@smithy/util-retry": "^4.2.8",
- "@smithy/util-stream": "^4.5.10",
- "@smithy/util-utf8": "^4.2.0",
+ "@aws-sdk/core": "^3.973.20",
+ "@aws-sdk/credential-provider-node": "^3.972.21",
+ "@aws-sdk/eventstream-handler-node": "^3.972.11",
+ "@aws-sdk/middleware-eventstream": "^3.972.8",
+ "@aws-sdk/middleware-host-header": "^3.972.8",
+ "@aws-sdk/middleware-logger": "^3.972.8",
+ "@aws-sdk/middleware-recursion-detection": "^3.972.8",
+ "@aws-sdk/middleware-user-agent": "^3.972.21",
+ "@aws-sdk/middleware-websocket": "^3.972.13",
+ "@aws-sdk/region-config-resolver": "^3.972.8",
+ "@aws-sdk/token-providers": "3.1011.0",
+ "@aws-sdk/types": "^3.973.6",
+ "@aws-sdk/util-endpoints": "^3.996.5",
+ "@aws-sdk/util-user-agent-browser": "^3.972.8",
+ "@aws-sdk/util-user-agent-node": "^3.973.7",
+ "@smithy/config-resolver": "^4.4.11",
+ "@smithy/core": "^3.23.11",
+ "@smithy/eventstream-serde-browser": "^4.2.12",
+ "@smithy/eventstream-serde-config-resolver": "^4.3.12",
+ "@smithy/eventstream-serde-node": "^4.2.12",
+ "@smithy/fetch-http-handler": "^5.3.15",
+ "@smithy/hash-node": "^4.2.12",
+ "@smithy/invalid-dependency": "^4.2.12",
+ "@smithy/middleware-content-length": "^4.2.12",
+ "@smithy/middleware-endpoint": "^4.4.25",
+ "@smithy/middleware-retry": "^4.4.42",
+ "@smithy/middleware-serde": "^4.2.14",
+ "@smithy/middleware-stack": "^4.2.12",
+ "@smithy/node-config-provider": "^4.3.12",
+ "@smithy/node-http-handler": "^4.4.16",
+ "@smithy/protocol-http": "^5.3.12",
+ "@smithy/smithy-client": "^4.12.5",
+ "@smithy/types": "^4.13.1",
+ "@smithy/url-parser": "^4.2.12",
+ "@smithy/util-base64": "^4.3.2",
+ "@smithy/util-body-length-browser": "^4.2.2",
+ "@smithy/util-body-length-node": "^4.2.3",
+ "@smithy/util-defaults-mode-browser": "^4.3.41",
+ "@smithy/util-defaults-mode-node": "^4.2.44",
+ "@smithy/util-endpoints": "^3.3.3",
+ "@smithy/util-middleware": "^4.2.12",
+ "@smithy/util-retry": "^4.2.12",
+ "@smithy/util-stream": "^4.5.19",
+ "@smithy/util-utf8": "^4.2.2",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/client-bedrock-runtime/node_modules/@aws-sdk/util-endpoints": {
+ "version": "3.996.5",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.5.tgz",
+ "integrity": "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/types": "^4.13.1",
+ "@smithy/url-parser": "^4.2.12",
+ "@smithy/util-endpoints": "^3.3.3",
"tslib": "^2.6.2"
},
"engines": {
@@ -3257,9 +3273,9 @@
}
},
"node_modules/@aws-sdk/client-bedrock-runtime/node_modules/@smithy/is-array-buffer": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz",
- "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz",
+ "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -3269,12 +3285,12 @@
}
},
"node_modules/@aws-sdk/client-bedrock-runtime/node_modules/@smithy/util-buffer-from": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz",
- "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz",
+ "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/is-array-buffer": "^4.2.0",
+ "@smithy/is-array-buffer": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -3282,12 +3298,12 @@
}
},
"node_modules/@aws-sdk/client-bedrock-runtime/node_modules/@smithy/util-utf8": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz",
- "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz",
+ "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-buffer-from": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -3465,24 +3481,24 @@
}
},
"node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-sdk-s3": {
- "version": "3.972.5",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.5.tgz",
- "integrity": "sha512-3IgeIDiQ15tmMBFIdJ1cTy3A9rXHGo+b9p22V38vA3MozeMyVC8VmCYdDLA0iMWo4VHA9LDJTgCM0+xU3wjBOg==",
+ "version": "3.972.20",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.20.tgz",
+ "integrity": "sha512-yhva/xL5H4tWQgsBjwV+RRD0ByCzg0TcByDCLp3GXdn/wlyRNfy8zsswDtCvr1WSKQkSQYlyEzPuWkJG0f5HvQ==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.973.5",
- "@aws-sdk/types": "^3.973.1",
- "@aws-sdk/util-arn-parser": "^3.972.2",
- "@smithy/core": "^3.22.0",
- "@smithy/node-config-provider": "^4.3.8",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/signature-v4": "^5.3.8",
- "@smithy/smithy-client": "^4.11.1",
- "@smithy/types": "^4.12.0",
- "@smithy/util-config-provider": "^4.2.0",
- "@smithy/util-middleware": "^4.2.8",
- "@smithy/util-stream": "^4.5.10",
- "@smithy/util-utf8": "^4.2.0",
+ "@aws-sdk/core": "^3.973.20",
+ "@aws-sdk/types": "^3.973.6",
+ "@aws-sdk/util-arn-parser": "^3.972.3",
+ "@smithy/core": "^3.23.11",
+ "@smithy/node-config-provider": "^4.3.12",
+ "@smithy/protocol-http": "^5.3.12",
+ "@smithy/signature-v4": "^5.3.12",
+ "@smithy/smithy-client": "^4.12.5",
+ "@smithy/types": "^4.13.1",
+ "@smithy/util-config-provider": "^4.2.2",
+ "@smithy/util-middleware": "^4.2.12",
+ "@smithy/util-stream": "^4.5.19",
+ "@smithy/util-utf8": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -3507,9 +3523,9 @@
}
},
"node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-arn-parser": {
- "version": "3.972.2",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.2.tgz",
- "integrity": "sha512-VkykWbqMjlSgBFDyrY3nOSqupMc6ivXuGmvci6Q3NnLq5kC+mKQe2QBZ4nrWRE/jqOxeFP2uYzLtwncYYcvQDg==",
+ "version": "3.972.3",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.3.tgz",
+ "integrity": "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -3519,9 +3535,9 @@
}
},
"node_modules/@aws-sdk/client-s3/node_modules/@smithy/is-array-buffer": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz",
- "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz",
+ "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -3531,12 +3547,12 @@
}
},
"node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-buffer-from": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz",
- "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz",
+ "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/is-array-buffer": "^4.2.0",
+ "@smithy/is-array-buffer": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -3544,115 +3560,12 @@
}
},
"node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-utf8": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz",
- "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz",
+ "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/util-buffer-from": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/client-sso": {
- "version": "3.982.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.982.0.tgz",
- "integrity": "sha512-qJrIiivmvujdGqJ0ldSUvhN3k3N7GtPesoOI1BSt0fNXovVnMz4C/JmnkhZihU7hJhDvxJaBROLYTU+lpild4w==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/sha256-browser": "5.2.0",
- "@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "^3.973.6",
- "@aws-sdk/middleware-host-header": "^3.972.3",
- "@aws-sdk/middleware-logger": "^3.972.3",
- "@aws-sdk/middleware-recursion-detection": "^3.972.3",
- "@aws-sdk/middleware-user-agent": "^3.972.6",
- "@aws-sdk/region-config-resolver": "^3.972.3",
- "@aws-sdk/types": "^3.973.1",
- "@aws-sdk/util-endpoints": "3.982.0",
- "@aws-sdk/util-user-agent-browser": "^3.972.3",
- "@aws-sdk/util-user-agent-node": "^3.972.4",
- "@smithy/config-resolver": "^4.4.6",
- "@smithy/core": "^3.22.0",
- "@smithy/fetch-http-handler": "^5.3.9",
- "@smithy/hash-node": "^4.2.8",
- "@smithy/invalid-dependency": "^4.2.8",
- "@smithy/middleware-content-length": "^4.2.8",
- "@smithy/middleware-endpoint": "^4.4.12",
- "@smithy/middleware-retry": "^4.4.29",
- "@smithy/middleware-serde": "^4.2.9",
- "@smithy/middleware-stack": "^4.2.8",
- "@smithy/node-config-provider": "^4.3.8",
- "@smithy/node-http-handler": "^4.4.8",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/smithy-client": "^4.11.1",
- "@smithy/types": "^4.12.0",
- "@smithy/url-parser": "^4.2.8",
- "@smithy/util-base64": "^4.3.0",
- "@smithy/util-body-length-browser": "^4.2.0",
- "@smithy/util-body-length-node": "^4.2.1",
- "@smithy/util-defaults-mode-browser": "^4.3.28",
- "@smithy/util-defaults-mode-node": "^4.2.31",
- "@smithy/util-endpoints": "^3.2.8",
- "@smithy/util-middleware": "^4.2.8",
- "@smithy/util-retry": "^4.2.8",
- "@smithy/util-utf8": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": {
- "version": "3.982.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz",
- "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "^3.973.1",
- "@smithy/types": "^4.12.0",
- "@smithy/url-parser": "^4.2.8",
- "@smithy/util-endpoints": "^3.2.8",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/client-sso/node_modules/@smithy/is-array-buffer": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz",
- "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-buffer-from": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz",
- "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/is-array-buffer": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-utf8": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz",
- "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-buffer-from": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -3660,23 +3573,23 @@
}
},
"node_modules/@aws-sdk/core": {
- "version": "3.973.6",
- "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.6.tgz",
- "integrity": "sha512-pz4ZOw3BLG0NdF25HoB9ymSYyPbMiIjwQJ2aROXRhAzt+b+EOxStfFv8s5iZyP6Kiw7aYhyWxj5G3NhmkoOTKw==",
+ "version": "3.973.20",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.20.tgz",
+ "integrity": "sha512-i3GuX+lowD892F3IuJf8o6AbyDupMTdyTxQrCJGcn71ni5hTZ82L4nQhcdumxZ7XPJRJJVHS/CR3uYOIIs0PVA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/types": "^3.973.1",
- "@aws-sdk/xml-builder": "^3.972.4",
- "@smithy/core": "^3.22.0",
- "@smithy/node-config-provider": "^4.3.8",
- "@smithy/property-provider": "^4.2.8",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/signature-v4": "^5.3.8",
- "@smithy/smithy-client": "^4.11.1",
- "@smithy/types": "^4.12.0",
- "@smithy/util-base64": "^4.3.0",
- "@smithy/util-middleware": "^4.2.8",
- "@smithy/util-utf8": "^4.2.0",
+ "@aws-sdk/types": "^3.973.6",
+ "@aws-sdk/xml-builder": "^3.972.11",
+ "@smithy/core": "^3.23.11",
+ "@smithy/node-config-provider": "^4.3.12",
+ "@smithy/property-provider": "^4.2.12",
+ "@smithy/protocol-http": "^5.3.12",
+ "@smithy/signature-v4": "^5.3.12",
+ "@smithy/smithy-client": "^4.12.5",
+ "@smithy/types": "^4.13.1",
+ "@smithy/util-base64": "^4.3.2",
+ "@smithy/util-middleware": "^4.2.12",
+ "@smithy/util-utf8": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -3684,9 +3597,9 @@
}
},
"node_modules/@aws-sdk/core/node_modules/@smithy/is-array-buffer": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz",
- "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz",
+ "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -3696,12 +3609,12 @@
}
},
"node_modules/@aws-sdk/core/node_modules/@smithy/util-buffer-from": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz",
- "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz",
+ "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/is-array-buffer": "^4.2.0",
+ "@smithy/is-array-buffer": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -3709,12 +3622,12 @@
}
},
"node_modules/@aws-sdk/core/node_modules/@smithy/util-utf8": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz",
- "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz",
+ "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-buffer-from": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -3722,12 +3635,12 @@
}
},
"node_modules/@aws-sdk/crc64-nvme": {
- "version": "3.972.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.0.tgz",
- "integrity": "sha512-ThlLhTqX68jvoIVv+pryOdb5coP1cX1/MaTbB9xkGDCbWbsqQcLqzPxuSoW1DCnAAIacmXCWpzUNOB9pv+xXQw==",
+ "version": "3.972.5",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.5.tgz",
+ "integrity": "sha512-2VbTstbjKdT+yKi8m7b3a9CiVac+pL/IY2PHJwsaGkkHmuuqkJZIErPck1h6P3T9ghQMLSdMPyW6Qp7Di5swFg==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.12.0",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -3735,15 +3648,15 @@
}
},
"node_modules/@aws-sdk/credential-provider-env": {
- "version": "3.972.4",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.4.tgz",
- "integrity": "sha512-/8dnc7+XNMmViEom2xsNdArQxQPSgy4Z/lm6qaFPTrMFesT1bV3PsBhb19n09nmxHdrtQskYmViddUIjUQElXg==",
+ "version": "3.972.18",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.18.tgz",
+ "integrity": "sha512-X0B8AlQY507i5DwjLByeU2Af4ARsl9Vr84koDcXCbAkplmU+1xBFWxEPrWRAoh56waBne/yJqEloSwvRf4x6XA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.973.6",
- "@aws-sdk/types": "^3.973.1",
- "@smithy/property-provider": "^4.2.8",
- "@smithy/types": "^4.12.0",
+ "@aws-sdk/core": "^3.973.20",
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/property-provider": "^4.2.12",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -3751,20 +3664,20 @@
}
},
"node_modules/@aws-sdk/credential-provider-http": {
- "version": "3.972.6",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.6.tgz",
- "integrity": "sha512-5ERWqRljiZv44AIdvIRQ3k+EAV0Sq2WeJHvXuK7gL7bovSxOf8Al7MLH7Eh3rdovH4KHFnlIty7J71mzvQBl5Q==",
+ "version": "3.972.20",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.20.tgz",
+ "integrity": "sha512-ey9Lelj001+oOfrbKmS6R2CJAiXX7QKY4Vj9VJv6L2eE6/VjD8DocHIoYqztTm70xDLR4E1jYPTKfIui+eRNDA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.973.6",
- "@aws-sdk/types": "^3.973.1",
- "@smithy/fetch-http-handler": "^5.3.9",
- "@smithy/node-http-handler": "^4.4.8",
- "@smithy/property-provider": "^4.2.8",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/smithy-client": "^4.11.1",
- "@smithy/types": "^4.12.0",
- "@smithy/util-stream": "^4.5.10",
+ "@aws-sdk/core": "^3.973.20",
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/fetch-http-handler": "^5.3.15",
+ "@smithy/node-http-handler": "^4.4.16",
+ "@smithy/property-provider": "^4.2.12",
+ "@smithy/protocol-http": "^5.3.12",
+ "@smithy/smithy-client": "^4.12.5",
+ "@smithy/types": "^4.13.1",
+ "@smithy/util-stream": "^4.5.19",
"tslib": "^2.6.2"
},
"engines": {
@@ -3772,272 +3685,66 @@
}
},
"node_modules/@aws-sdk/credential-provider-ini": {
- "version": "3.972.4",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.4.tgz",
- "integrity": "sha512-eRUg+3HaUKuXWn/lEMirdiA5HOKmEl8hEHVuszIDt2MMBUKgVX5XNGmb3XmbgU17h6DZ+RtjbxQpjhz3SbTjZg==",
+ "version": "3.972.20",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.20.tgz",
+ "integrity": "sha512-5flXSnKHMloObNF+9N0cupKegnH1Z37cdVlpETVgx8/rAhCe+VNlkcZH3HDg2SDn9bI765S+rhNPXGDJJPfbtA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.973.6",
- "@aws-sdk/credential-provider-env": "^3.972.4",
- "@aws-sdk/credential-provider-http": "^3.972.6",
- "@aws-sdk/credential-provider-login": "^3.972.4",
- "@aws-sdk/credential-provider-process": "^3.972.4",
- "@aws-sdk/credential-provider-sso": "^3.972.4",
- "@aws-sdk/credential-provider-web-identity": "^3.972.4",
- "@aws-sdk/nested-clients": "3.982.0",
- "@aws-sdk/types": "^3.973.1",
- "@smithy/credential-provider-imds": "^4.2.8",
- "@smithy/property-provider": "^4.2.8",
- "@smithy/shared-ini-file-loader": "^4.4.3",
- "@smithy/types": "^4.12.0",
+ "@aws-sdk/core": "^3.973.20",
+ "@aws-sdk/credential-provider-env": "^3.972.18",
+ "@aws-sdk/credential-provider-http": "^3.972.20",
+ "@aws-sdk/credential-provider-login": "^3.972.20",
+ "@aws-sdk/credential-provider-process": "^3.972.18",
+ "@aws-sdk/credential-provider-sso": "^3.972.20",
+ "@aws-sdk/credential-provider-web-identity": "^3.972.20",
+ "@aws-sdk/nested-clients": "^3.996.10",
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/credential-provider-imds": "^4.2.12",
+ "@smithy/property-provider": "^4.2.12",
+ "@smithy/shared-ini-file-loader": "^4.4.7",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=20.0.0"
}
},
- "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/nested-clients": {
- "version": "3.982.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.982.0.tgz",
- "integrity": "sha512-VVkaH27digrJfdVrT64rjkllvOp4oRiZuuJvrylLXAKl18ujToJR7AqpDldL/LS63RVne3QWIpkygIymxFtliQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/sha256-browser": "5.2.0",
- "@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "^3.973.6",
- "@aws-sdk/middleware-host-header": "^3.972.3",
- "@aws-sdk/middleware-logger": "^3.972.3",
- "@aws-sdk/middleware-recursion-detection": "^3.972.3",
- "@aws-sdk/middleware-user-agent": "^3.972.6",
- "@aws-sdk/region-config-resolver": "^3.972.3",
- "@aws-sdk/types": "^3.973.1",
- "@aws-sdk/util-endpoints": "3.982.0",
- "@aws-sdk/util-user-agent-browser": "^3.972.3",
- "@aws-sdk/util-user-agent-node": "^3.972.4",
- "@smithy/config-resolver": "^4.4.6",
- "@smithy/core": "^3.22.0",
- "@smithy/fetch-http-handler": "^5.3.9",
- "@smithy/hash-node": "^4.2.8",
- "@smithy/invalid-dependency": "^4.2.8",
- "@smithy/middleware-content-length": "^4.2.8",
- "@smithy/middleware-endpoint": "^4.4.12",
- "@smithy/middleware-retry": "^4.4.29",
- "@smithy/middleware-serde": "^4.2.9",
- "@smithy/middleware-stack": "^4.2.8",
- "@smithy/node-config-provider": "^4.3.8",
- "@smithy/node-http-handler": "^4.4.8",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/smithy-client": "^4.11.1",
- "@smithy/types": "^4.12.0",
- "@smithy/url-parser": "^4.2.8",
- "@smithy/util-base64": "^4.3.0",
- "@smithy/util-body-length-browser": "^4.2.0",
- "@smithy/util-body-length-node": "^4.2.1",
- "@smithy/util-defaults-mode-browser": "^4.3.28",
- "@smithy/util-defaults-mode-node": "^4.2.31",
- "@smithy/util-endpoints": "^3.2.8",
- "@smithy/util-middleware": "^4.2.8",
- "@smithy/util-retry": "^4.2.8",
- "@smithy/util-utf8": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-endpoints": {
- "version": "3.982.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz",
- "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "^3.973.1",
- "@smithy/types": "^4.12.0",
- "@smithy/url-parser": "^4.2.8",
- "@smithy/util-endpoints": "^3.2.8",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/is-array-buffer": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz",
- "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/util-buffer-from": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz",
- "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/is-array-buffer": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/util-utf8": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz",
- "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/util-buffer-from": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
"node_modules/@aws-sdk/credential-provider-login": {
- "version": "3.972.4",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.4.tgz",
- "integrity": "sha512-nLGjXuvWWDlQAp505xIONI7Gam0vw2p7Qu3P6on/W2q7rjJXtYjtpHbcsaOjJ/pAju3eTvEQuSuRedcRHVQIAQ==",
+ "version": "3.972.20",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.20.tgz",
+ "integrity": "sha512-gEWo54nfqp2jABMu6HNsjVC4hDLpg9HC8IKSJnp0kqWtxIJYHTmiLSsIfI4ScQjxEwpB+jOOH8dOLax1+hy/Hw==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.973.6",
- "@aws-sdk/nested-clients": "3.982.0",
- "@aws-sdk/types": "^3.973.1",
- "@smithy/property-provider": "^4.2.8",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/shared-ini-file-loader": "^4.4.3",
- "@smithy/types": "^4.12.0",
+ "@aws-sdk/core": "^3.973.20",
+ "@aws-sdk/nested-clients": "^3.996.10",
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/property-provider": "^4.2.12",
+ "@smithy/protocol-http": "^5.3.12",
+ "@smithy/shared-ini-file-loader": "^4.4.7",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=20.0.0"
}
},
- "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/nested-clients": {
- "version": "3.982.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.982.0.tgz",
- "integrity": "sha512-VVkaH27digrJfdVrT64rjkllvOp4oRiZuuJvrylLXAKl18ujToJR7AqpDldL/LS63RVne3QWIpkygIymxFtliQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/sha256-browser": "5.2.0",
- "@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "^3.973.6",
- "@aws-sdk/middleware-host-header": "^3.972.3",
- "@aws-sdk/middleware-logger": "^3.972.3",
- "@aws-sdk/middleware-recursion-detection": "^3.972.3",
- "@aws-sdk/middleware-user-agent": "^3.972.6",
- "@aws-sdk/region-config-resolver": "^3.972.3",
- "@aws-sdk/types": "^3.973.1",
- "@aws-sdk/util-endpoints": "3.982.0",
- "@aws-sdk/util-user-agent-browser": "^3.972.3",
- "@aws-sdk/util-user-agent-node": "^3.972.4",
- "@smithy/config-resolver": "^4.4.6",
- "@smithy/core": "^3.22.0",
- "@smithy/fetch-http-handler": "^5.3.9",
- "@smithy/hash-node": "^4.2.8",
- "@smithy/invalid-dependency": "^4.2.8",
- "@smithy/middleware-content-length": "^4.2.8",
- "@smithy/middleware-endpoint": "^4.4.12",
- "@smithy/middleware-retry": "^4.4.29",
- "@smithy/middleware-serde": "^4.2.9",
- "@smithy/middleware-stack": "^4.2.8",
- "@smithy/node-config-provider": "^4.3.8",
- "@smithy/node-http-handler": "^4.4.8",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/smithy-client": "^4.11.1",
- "@smithy/types": "^4.12.0",
- "@smithy/url-parser": "^4.2.8",
- "@smithy/util-base64": "^4.3.0",
- "@smithy/util-body-length-browser": "^4.2.0",
- "@smithy/util-body-length-node": "^4.2.1",
- "@smithy/util-defaults-mode-browser": "^4.3.28",
- "@smithy/util-defaults-mode-node": "^4.2.31",
- "@smithy/util-endpoints": "^3.2.8",
- "@smithy/util-middleware": "^4.2.8",
- "@smithy/util-retry": "^4.2.8",
- "@smithy/util-utf8": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/util-endpoints": {
- "version": "3.982.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz",
- "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "^3.973.1",
- "@smithy/types": "^4.12.0",
- "@smithy/url-parser": "^4.2.8",
- "@smithy/util-endpoints": "^3.2.8",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/is-array-buffer": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz",
- "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-buffer-from": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz",
- "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/is-array-buffer": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-utf8": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz",
- "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/util-buffer-from": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
"node_modules/@aws-sdk/credential-provider-node": {
- "version": "3.972.5",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.5.tgz",
- "integrity": "sha512-VWXKgSISQCI2GKN3zakTNHSiZ0+mux7v6YHmmbLQp/o3fvYUQJmKGcLZZzg2GFA+tGGBStplra9VFNf/WwxpYg==",
+ "version": "3.972.21",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.21.tgz",
+ "integrity": "sha512-hah8if3/B/Q+LBYN5FukyQ1Mym6PLPDsBOBsIgNEYD6wLyZg0UmUF/OKIVC3nX9XH8TfTPuITK+7N/jenVACWA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/credential-provider-env": "^3.972.4",
- "@aws-sdk/credential-provider-http": "^3.972.6",
- "@aws-sdk/credential-provider-ini": "^3.972.4",
- "@aws-sdk/credential-provider-process": "^3.972.4",
- "@aws-sdk/credential-provider-sso": "^3.972.4",
- "@aws-sdk/credential-provider-web-identity": "^3.972.4",
- "@aws-sdk/types": "^3.973.1",
- "@smithy/credential-provider-imds": "^4.2.8",
- "@smithy/property-provider": "^4.2.8",
- "@smithy/shared-ini-file-loader": "^4.4.3",
- "@smithy/types": "^4.12.0",
+ "@aws-sdk/credential-provider-env": "^3.972.18",
+ "@aws-sdk/credential-provider-http": "^3.972.20",
+ "@aws-sdk/credential-provider-ini": "^3.972.20",
+ "@aws-sdk/credential-provider-process": "^3.972.18",
+ "@aws-sdk/credential-provider-sso": "^3.972.20",
+ "@aws-sdk/credential-provider-web-identity": "^3.972.20",
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/credential-provider-imds": "^4.2.12",
+ "@smithy/property-provider": "^4.2.12",
+ "@smithy/shared-ini-file-loader": "^4.4.7",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -4045,16 +3752,16 @@
}
},
"node_modules/@aws-sdk/credential-provider-process": {
- "version": "3.972.4",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.4.tgz",
- "integrity": "sha512-TCZpWUnBQN1YPk6grvd5x419OfXjHvhj5Oj44GYb84dOVChpg/+2VoEj+YVA4F4E/6huQPNnX7UYbTtxJqgihw==",
+ "version": "3.972.18",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.18.tgz",
+ "integrity": "sha512-Tpl7SRaPoOLT32jbTWchPsn52hYYgJ0kpiFgnwk8pxTANQdUymVSZkzFvv1+oOgZm1CrbQUP9MBeoMZ9IzLZjA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.973.6",
- "@aws-sdk/types": "^3.973.1",
- "@smithy/property-provider": "^4.2.8",
- "@smithy/shared-ini-file-loader": "^4.4.3",
- "@smithy/types": "^4.12.0",
+ "@aws-sdk/core": "^3.973.20",
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/property-provider": "^4.2.12",
+ "@smithy/shared-ini-file-loader": "^4.4.7",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -4062,67 +3769,18 @@
}
},
"node_modules/@aws-sdk/credential-provider-sso": {
- "version": "3.972.4",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.4.tgz",
- "integrity": "sha512-wzsGwv9mKlwJ3vHLyembBvGE/5nPUIwRR2I51B1cBV4Cb4ql9nIIfpmHzm050XYTY5fqTOKJQnhLj7zj89VG8g==",
+ "version": "3.972.20",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.20.tgz",
+ "integrity": "sha512-p+R+PYR5Z7Gjqf/6pvbCnzEHcqPCpLzR7Yf127HjJ6EAb4hUcD+qsNRnuww1sB/RmSeCLxyay8FMyqREw4p1RA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/client-sso": "3.982.0",
- "@aws-sdk/core": "^3.973.6",
- "@aws-sdk/token-providers": "3.982.0",
- "@aws-sdk/types": "^3.973.1",
- "@smithy/property-provider": "^4.2.8",
- "@smithy/shared-ini-file-loader": "^4.4.3",
- "@smithy/types": "^4.12.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/nested-clients": {
- "version": "3.982.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.982.0.tgz",
- "integrity": "sha512-VVkaH27digrJfdVrT64rjkllvOp4oRiZuuJvrylLXAKl18ujToJR7AqpDldL/LS63RVne3QWIpkygIymxFtliQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/sha256-browser": "5.2.0",
- "@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "^3.973.6",
- "@aws-sdk/middleware-host-header": "^3.972.3",
- "@aws-sdk/middleware-logger": "^3.972.3",
- "@aws-sdk/middleware-recursion-detection": "^3.972.3",
- "@aws-sdk/middleware-user-agent": "^3.972.6",
- "@aws-sdk/region-config-resolver": "^3.972.3",
- "@aws-sdk/types": "^3.973.1",
- "@aws-sdk/util-endpoints": "3.982.0",
- "@aws-sdk/util-user-agent-browser": "^3.972.3",
- "@aws-sdk/util-user-agent-node": "^3.972.4",
- "@smithy/config-resolver": "^4.4.6",
- "@smithy/core": "^3.22.0",
- "@smithy/fetch-http-handler": "^5.3.9",
- "@smithy/hash-node": "^4.2.8",
- "@smithy/invalid-dependency": "^4.2.8",
- "@smithy/middleware-content-length": "^4.2.8",
- "@smithy/middleware-endpoint": "^4.4.12",
- "@smithy/middleware-retry": "^4.4.29",
- "@smithy/middleware-serde": "^4.2.9",
- "@smithy/middleware-stack": "^4.2.8",
- "@smithy/node-config-provider": "^4.3.8",
- "@smithy/node-http-handler": "^4.4.8",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/smithy-client": "^4.11.1",
- "@smithy/types": "^4.12.0",
- "@smithy/url-parser": "^4.2.8",
- "@smithy/util-base64": "^4.3.0",
- "@smithy/util-body-length-browser": "^4.2.0",
- "@smithy/util-body-length-node": "^4.2.1",
- "@smithy/util-defaults-mode-browser": "^4.3.28",
- "@smithy/util-defaults-mode-node": "^4.2.31",
- "@smithy/util-endpoints": "^3.2.8",
- "@smithy/util-middleware": "^4.2.8",
- "@smithy/util-retry": "^4.2.8",
- "@smithy/util-utf8": "^4.2.0",
+ "@aws-sdk/core": "^3.973.20",
+ "@aws-sdk/nested-clients": "^3.996.10",
+ "@aws-sdk/token-providers": "3.1009.0",
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/property-provider": "^4.2.12",
+ "@smithy/shared-ini-file-loader": "^4.4.7",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -4130,207 +3788,50 @@
}
},
"node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": {
- "version": "3.982.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.982.0.tgz",
- "integrity": "sha512-v3M0KYp2TVHYHNBT7jHD9lLTWAdS9CaWJ2jboRKt0WAB65bA7iUEpR+k4VqKYtpQN4+8kKSc4w+K6kUNZkHKQw==",
+ "version": "3.1009.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1009.0.tgz",
+ "integrity": "sha512-KCPLuTqN9u0Rr38Arln78fRG9KXpzsPWmof+PZzfAHMMQq2QED6YjQrkrfiH7PDefLWEposY1o4/eGwrmKA4JA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.973.6",
- "@aws-sdk/nested-clients": "3.982.0",
- "@aws-sdk/types": "^3.973.1",
- "@smithy/property-provider": "^4.2.8",
- "@smithy/shared-ini-file-loader": "^4.4.3",
- "@smithy/types": "^4.12.0",
+ "@aws-sdk/core": "^3.973.20",
+ "@aws-sdk/nested-clients": "^3.996.10",
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/property-provider": "^4.2.12",
+ "@smithy/shared-ini-file-loader": "^4.4.7",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=20.0.0"
}
},
- "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/util-endpoints": {
- "version": "3.982.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz",
- "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "^3.973.1",
- "@smithy/types": "^4.12.0",
- "@smithy/url-parser": "^4.2.8",
- "@smithy/util-endpoints": "^3.2.8",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/is-array-buffer": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz",
- "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/util-buffer-from": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz",
- "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/is-array-buffer": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/util-utf8": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz",
- "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/util-buffer-from": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
"node_modules/@aws-sdk/credential-provider-web-identity": {
- "version": "3.972.4",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.4.tgz",
- "integrity": "sha512-hIzw2XzrG8jzsUSEatehmpkd5rWzASg5IHUfA+m01k/RtvfAML7ZJVVohuKdhAYx+wV2AThLiQJVzqn7F0khrw==",
+ "version": "3.972.20",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.20.tgz",
+ "integrity": "sha512-rWCmh8o7QY4CsUj63qopzMzkDq/yPpkrpb+CnjBEFSOg/02T/we7sSTVg4QsDiVS9uwZ8VyONhq98qt+pIh3KA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.973.6",
- "@aws-sdk/nested-clients": "3.982.0",
- "@aws-sdk/types": "^3.973.1",
- "@smithy/property-provider": "^4.2.8",
- "@smithy/shared-ini-file-loader": "^4.4.3",
- "@smithy/types": "^4.12.0",
+ "@aws-sdk/core": "^3.973.20",
+ "@aws-sdk/nested-clients": "^3.996.10",
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/property-provider": "^4.2.12",
+ "@smithy/shared-ini-file-loader": "^4.4.7",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=20.0.0"
}
},
- "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@aws-sdk/nested-clients": {
- "version": "3.982.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.982.0.tgz",
- "integrity": "sha512-VVkaH27digrJfdVrT64rjkllvOp4oRiZuuJvrylLXAKl18ujToJR7AqpDldL/LS63RVne3QWIpkygIymxFtliQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/sha256-browser": "5.2.0",
- "@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "^3.973.6",
- "@aws-sdk/middleware-host-header": "^3.972.3",
- "@aws-sdk/middleware-logger": "^3.972.3",
- "@aws-sdk/middleware-recursion-detection": "^3.972.3",
- "@aws-sdk/middleware-user-agent": "^3.972.6",
- "@aws-sdk/region-config-resolver": "^3.972.3",
- "@aws-sdk/types": "^3.973.1",
- "@aws-sdk/util-endpoints": "3.982.0",
- "@aws-sdk/util-user-agent-browser": "^3.972.3",
- "@aws-sdk/util-user-agent-node": "^3.972.4",
- "@smithy/config-resolver": "^4.4.6",
- "@smithy/core": "^3.22.0",
- "@smithy/fetch-http-handler": "^5.3.9",
- "@smithy/hash-node": "^4.2.8",
- "@smithy/invalid-dependency": "^4.2.8",
- "@smithy/middleware-content-length": "^4.2.8",
- "@smithy/middleware-endpoint": "^4.4.12",
- "@smithy/middleware-retry": "^4.4.29",
- "@smithy/middleware-serde": "^4.2.9",
- "@smithy/middleware-stack": "^4.2.8",
- "@smithy/node-config-provider": "^4.3.8",
- "@smithy/node-http-handler": "^4.4.8",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/smithy-client": "^4.11.1",
- "@smithy/types": "^4.12.0",
- "@smithy/url-parser": "^4.2.8",
- "@smithy/util-base64": "^4.3.0",
- "@smithy/util-body-length-browser": "^4.2.0",
- "@smithy/util-body-length-node": "^4.2.1",
- "@smithy/util-defaults-mode-browser": "^4.3.28",
- "@smithy/util-defaults-mode-node": "^4.2.31",
- "@smithy/util-endpoints": "^3.2.8",
- "@smithy/util-middleware": "^4.2.8",
- "@smithy/util-retry": "^4.2.8",
- "@smithy/util-utf8": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@aws-sdk/util-endpoints": {
- "version": "3.982.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz",
- "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "^3.973.1",
- "@smithy/types": "^4.12.0",
- "@smithy/url-parser": "^4.2.8",
- "@smithy/util-endpoints": "^3.2.8",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/is-array-buffer": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz",
- "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/util-buffer-from": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz",
- "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/is-array-buffer": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/util-utf8": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz",
- "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/util-buffer-from": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
"node_modules/@aws-sdk/eventstream-handler-node": {
- "version": "3.972.3",
- "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.3.tgz",
- "integrity": "sha512-uQbkXcfEj4+TrxTmZkSwsYRE9nujx9b6WeLoQkDsldzEpcQhtKIz/RHSB4lWe7xzDMfGCLUkwmSJjetGVcrhCw==",
+ "version": "3.972.11",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.11.tgz",
+ "integrity": "sha512-2IrLrOruRr1NhTK0vguBL1gCWv1pu4bf4KaqpsA+/vCJpFEbvXFawn71GvCzk1wyjnDUsemtKypqoKGv4cSGbA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/types": "^3.973.1",
- "@smithy/eventstream-codec": "^4.2.8",
- "@smithy/types": "^4.12.0",
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/eventstream-codec": "^4.2.12",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -4368,14 +3869,14 @@
}
},
"node_modules/@aws-sdk/middleware-eventstream": {
- "version": "3.972.3",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.3.tgz",
- "integrity": "sha512-pbvZ6Ye/Ks6BAZPa3RhsNjHrvxU9li25PMhSdDpbX0jzdpKpAkIR65gXSNKmA/REnSdEMWSD4vKUW+5eMFzB6w==",
+ "version": "3.972.8",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.8.tgz",
+ "integrity": "sha512-r+oP+tbCxgqXVC3pu3MUVePgSY0ILMjA+aEwOosS77m3/DRbtvHrHwqvMcw+cjANMeGzJ+i0ar+n77KXpRA8RQ==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/types": "^3.973.1",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/types": "^4.12.0",
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/protocol-http": "^5.3.12",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -4398,24 +3899,24 @@
}
},
"node_modules/@aws-sdk/middleware-flexible-checksums": {
- "version": "3.972.3",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.972.3.tgz",
- "integrity": "sha512-MkNGJ6qB9kpsLwL18kC/ZXppsJbftHVGCisqpEVbTQsum8CLYDX1Bmp/IvhRGNxsqCO2w9/4PwhDKBjG3Uvr4Q==",
+ "version": "3.974.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.974.0.tgz",
+ "integrity": "sha512-BmdDjqvnuYaC4SY7ypHLXfCSsGYGUZkjCLSZyUAAYn1YT28vbNMJNDwhlfkvvE+hQHG5RJDlEmYuvBxcB9jX1g==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/crc32": "5.2.0",
"@aws-crypto/crc32c": "5.2.0",
"@aws-crypto/util": "5.2.0",
- "@aws-sdk/core": "^3.973.5",
- "@aws-sdk/crc64-nvme": "3.972.0",
- "@aws-sdk/types": "^3.973.1",
- "@smithy/is-array-buffer": "^4.2.0",
- "@smithy/node-config-provider": "^4.3.8",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/types": "^4.12.0",
- "@smithy/util-middleware": "^4.2.8",
- "@smithy/util-stream": "^4.5.10",
- "@smithy/util-utf8": "^4.2.0",
+ "@aws-sdk/core": "^3.973.20",
+ "@aws-sdk/crc64-nvme": "^3.972.5",
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/is-array-buffer": "^4.2.2",
+ "@smithy/node-config-provider": "^4.3.12",
+ "@smithy/protocol-http": "^5.3.12",
+ "@smithy/types": "^4.13.1",
+ "@smithy/util-middleware": "^4.2.12",
+ "@smithy/util-stream": "^4.5.19",
+ "@smithy/util-utf8": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -4423,9 +3924,9 @@
}
},
"node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/is-array-buffer": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz",
- "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz",
+ "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -4435,12 +3936,12 @@
}
},
"node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/util-buffer-from": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz",
- "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz",
+ "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/is-array-buffer": "^4.2.0",
+ "@smithy/is-array-buffer": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -4448,12 +3949,12 @@
}
},
"node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/util-utf8": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz",
- "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz",
+ "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-buffer-from": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -4461,14 +3962,14 @@
}
},
"node_modules/@aws-sdk/middleware-host-header": {
- "version": "3.972.3",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.3.tgz",
- "integrity": "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA==",
+ "version": "3.972.8",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.8.tgz",
+ "integrity": "sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/types": "^3.973.1",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/types": "^4.12.0",
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/protocol-http": "^5.3.12",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -4490,13 +3991,13 @@
}
},
"node_modules/@aws-sdk/middleware-logger": {
- "version": "3.972.3",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.3.tgz",
- "integrity": "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA==",
+ "version": "3.972.8",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.8.tgz",
+ "integrity": "sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/types": "^3.973.1",
- "@smithy/types": "^4.12.0",
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -4504,15 +4005,15 @@
}
},
"node_modules/@aws-sdk/middleware-recursion-detection": {
- "version": "3.972.3",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.3.tgz",
- "integrity": "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q==",
+ "version": "3.972.8",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.8.tgz",
+ "integrity": "sha512-BnnvYs2ZEpdlmZ2PNlV2ZyQ8j8AEkMTjN79y/YA475ER1ByFYrkVR85qmhni8oeTaJcDqbx364wDpitDAA/wCA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/types": "^3.973.1",
+ "@aws-sdk/types": "^3.973.6",
"@aws/lambda-invoke-store": "^0.2.2",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/types": "^4.12.0",
+ "@smithy/protocol-http": "^5.3.12",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -4632,17 +4133,18 @@
}
},
"node_modules/@aws-sdk/middleware-user-agent": {
- "version": "3.972.6",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.6.tgz",
- "integrity": "sha512-TehLN8W/kivl0U9HcS+keryElEWORROpghDXZBLfnb40DXM7hx/i+7OOjkogXQOF3QtUraJVRkHQ07bPhrWKlw==",
+ "version": "3.972.21",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.21.tgz",
+ "integrity": "sha512-62XRl1GDYPpkt7cx1AX1SPy9wgNE9Iw/NPuurJu4lmhCWS7sGKO+kS53TQ8eRmIxy3skmvNInnk0ZbWrU5Dpyg==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.973.6",
- "@aws-sdk/types": "^3.973.1",
- "@aws-sdk/util-endpoints": "3.982.0",
- "@smithy/core": "^3.22.0",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/types": "^4.12.0",
+ "@aws-sdk/core": "^3.973.20",
+ "@aws-sdk/types": "^3.973.6",
+ "@aws-sdk/util-endpoints": "^3.996.5",
+ "@smithy/core": "^3.23.11",
+ "@smithy/protocol-http": "^5.3.12",
+ "@smithy/types": "^4.13.1",
+ "@smithy/util-retry": "^4.2.12",
"tslib": "^2.6.2"
},
"engines": {
@@ -4650,15 +4152,15 @@
}
},
"node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/util-endpoints": {
- "version": "3.982.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz",
- "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==",
+ "version": "3.996.5",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.5.tgz",
+ "integrity": "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/types": "^3.973.1",
- "@smithy/types": "^4.12.0",
- "@smithy/url-parser": "^4.2.8",
- "@smithy/util-endpoints": "^3.2.8",
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/types": "^4.13.1",
+ "@smithy/url-parser": "^4.2.12",
+ "@smithy/util-endpoints": "^3.3.3",
"tslib": "^2.6.2"
},
"engines": {
@@ -4666,20 +4168,22 @@
}
},
"node_modules/@aws-sdk/middleware-websocket": {
- "version": "3.972.3",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.3.tgz",
- "integrity": "sha512-/BjMbtOM9lsgdNgRZWUL5oCV6Ocfx1vcK/C5xO5/t/gCk6IwR9JFWMilbk6K6Buq5F84/lkngqcCKU2SRkAmOg==",
+ "version": "3.972.13",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.13.tgz",
+ "integrity": "sha512-Gp6EWIqHX5wmsOR5ZxWyyzEU8P0xBdSxkm6VHEwXwBqScKZ7QWRoj6ZmHpr+S44EYb5tuzGya4ottsogSu2W3A==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/types": "^3.973.1",
- "@aws-sdk/util-format-url": "^3.972.3",
- "@smithy/eventstream-codec": "^4.2.8",
- "@smithy/eventstream-serde-browser": "^4.2.8",
- "@smithy/fetch-http-handler": "^5.3.9",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/signature-v4": "^5.3.8",
- "@smithy/types": "^4.12.0",
- "@smithy/util-hex-encoding": "^4.2.0",
+ "@aws-sdk/types": "^3.973.6",
+ "@aws-sdk/util-format-url": "^3.972.8",
+ "@smithy/eventstream-codec": "^4.2.12",
+ "@smithy/eventstream-serde-browser": "^4.2.12",
+ "@smithy/fetch-http-handler": "^5.3.15",
+ "@smithy/protocol-http": "^5.3.12",
+ "@smithy/signature-v4": "^5.3.12",
+ "@smithy/types": "^4.13.1",
+ "@smithy/util-base64": "^4.3.2",
+ "@smithy/util-hex-encoding": "^4.2.2",
+ "@smithy/util-utf8": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -4687,63 +4191,117 @@
}
},
"node_modules/@aws-sdk/middleware-websocket/node_modules/@aws-sdk/util-format-url": {
- "version": "3.972.3",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.972.3.tgz",
- "integrity": "sha512-n7F2ycckcKFXa01vAsT/SJdjFHfKH9s96QHcs5gn8AaaigASICeME8WdUL9uBp8XV/OVwEt8+6gzn6KFUgQa8g==",
+ "version": "3.972.8",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.972.8.tgz",
+ "integrity": "sha512-J6DS9oocrgxM8xlUTTmQOuwRF6rnAGEujAN9SAzllcrQmwn5iJ58ogxy3SEhD0Q7JZvlA5jvIXBkpQRqEqlE9A==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/types": "^3.973.1",
- "@smithy/querystring-builder": "^4.2.8",
- "@smithy/types": "^4.12.0",
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/querystring-builder": "^4.2.12",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=20.0.0"
}
},
+ "node_modules/@aws-sdk/middleware-websocket/node_modules/@smithy/is-array-buffer": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz",
+ "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-websocket/node_modules/@smithy/util-buffer-from": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz",
+ "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/is-array-buffer": "^4.2.2",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-websocket/node_modules/@smithy/util-utf8": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz",
+ "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/util-buffer-from": "^4.2.2",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"node_modules/@aws-sdk/nested-clients": {
- "version": "3.980.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.980.0.tgz",
- "integrity": "sha512-/dONY5xc5/CCKzOqHZCTidtAR4lJXWkGefXvTRKdSKMGaYbbKsxDckisd6GfnvPSLxWtvQzwgRGRutMRoYUApQ==",
+ "version": "3.996.10",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.10.tgz",
+ "integrity": "sha512-SlDol5Z+C7Ivnc2rKGqiqfSUmUZzY1qHfVs9myt/nxVwswgfpjdKahyTzLTx802Zfq0NFRs7AejwKzzzl5Co2w==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "^3.973.5",
- "@aws-sdk/middleware-host-header": "^3.972.3",
- "@aws-sdk/middleware-logger": "^3.972.3",
- "@aws-sdk/middleware-recursion-detection": "^3.972.3",
- "@aws-sdk/middleware-user-agent": "^3.972.5",
- "@aws-sdk/region-config-resolver": "^3.972.3",
- "@aws-sdk/types": "^3.973.1",
- "@aws-sdk/util-endpoints": "3.980.0",
- "@aws-sdk/util-user-agent-browser": "^3.972.3",
- "@aws-sdk/util-user-agent-node": "^3.972.3",
- "@smithy/config-resolver": "^4.4.6",
- "@smithy/core": "^3.22.0",
- "@smithy/fetch-http-handler": "^5.3.9",
- "@smithy/hash-node": "^4.2.8",
- "@smithy/invalid-dependency": "^4.2.8",
- "@smithy/middleware-content-length": "^4.2.8",
- "@smithy/middleware-endpoint": "^4.4.12",
- "@smithy/middleware-retry": "^4.4.29",
- "@smithy/middleware-serde": "^4.2.9",
- "@smithy/middleware-stack": "^4.2.8",
- "@smithy/node-config-provider": "^4.3.8",
- "@smithy/node-http-handler": "^4.4.8",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/smithy-client": "^4.11.1",
- "@smithy/types": "^4.12.0",
- "@smithy/url-parser": "^4.2.8",
- "@smithy/util-base64": "^4.3.0",
- "@smithy/util-body-length-browser": "^4.2.0",
- "@smithy/util-body-length-node": "^4.2.1",
- "@smithy/util-defaults-mode-browser": "^4.3.28",
- "@smithy/util-defaults-mode-node": "^4.2.31",
- "@smithy/util-endpoints": "^3.2.8",
- "@smithy/util-middleware": "^4.2.8",
- "@smithy/util-retry": "^4.2.8",
- "@smithy/util-utf8": "^4.2.0",
+ "@aws-sdk/core": "^3.973.20",
+ "@aws-sdk/middleware-host-header": "^3.972.8",
+ "@aws-sdk/middleware-logger": "^3.972.8",
+ "@aws-sdk/middleware-recursion-detection": "^3.972.8",
+ "@aws-sdk/middleware-user-agent": "^3.972.21",
+ "@aws-sdk/region-config-resolver": "^3.972.8",
+ "@aws-sdk/types": "^3.973.6",
+ "@aws-sdk/util-endpoints": "^3.996.5",
+ "@aws-sdk/util-user-agent-browser": "^3.972.8",
+ "@aws-sdk/util-user-agent-node": "^3.973.7",
+ "@smithy/config-resolver": "^4.4.11",
+ "@smithy/core": "^3.23.11",
+ "@smithy/fetch-http-handler": "^5.3.15",
+ "@smithy/hash-node": "^4.2.12",
+ "@smithy/invalid-dependency": "^4.2.12",
+ "@smithy/middleware-content-length": "^4.2.12",
+ "@smithy/middleware-endpoint": "^4.4.25",
+ "@smithy/middleware-retry": "^4.4.42",
+ "@smithy/middleware-serde": "^4.2.14",
+ "@smithy/middleware-stack": "^4.2.12",
+ "@smithy/node-config-provider": "^4.3.12",
+ "@smithy/node-http-handler": "^4.4.16",
+ "@smithy/protocol-http": "^5.3.12",
+ "@smithy/smithy-client": "^4.12.5",
+ "@smithy/types": "^4.13.1",
+ "@smithy/url-parser": "^4.2.12",
+ "@smithy/util-base64": "^4.3.2",
+ "@smithy/util-body-length-browser": "^4.2.2",
+ "@smithy/util-body-length-node": "^4.2.3",
+ "@smithy/util-defaults-mode-browser": "^4.3.41",
+ "@smithy/util-defaults-mode-node": "^4.2.44",
+ "@smithy/util-endpoints": "^3.3.3",
+ "@smithy/util-middleware": "^4.2.12",
+ "@smithy/util-retry": "^4.2.12",
+ "@smithy/util-utf8": "^4.2.2",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-endpoints": {
+ "version": "3.996.5",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.5.tgz",
+ "integrity": "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/types": "^4.13.1",
+ "@smithy/url-parser": "^4.2.12",
+ "@smithy/util-endpoints": "^3.3.3",
"tslib": "^2.6.2"
},
"engines": {
@@ -4751,9 +4309,9 @@
}
},
"node_modules/@aws-sdk/nested-clients/node_modules/@smithy/is-array-buffer": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz",
- "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz",
+ "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -4763,12 +4321,12 @@
}
},
"node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-buffer-from": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz",
- "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz",
+ "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/is-array-buffer": "^4.2.0",
+ "@smithy/is-array-buffer": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -4776,12 +4334,12 @@
}
},
"node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-utf8": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz",
- "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz",
+ "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-buffer-from": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -4789,15 +4347,15 @@
}
},
"node_modules/@aws-sdk/region-config-resolver": {
- "version": "3.972.3",
- "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.3.tgz",
- "integrity": "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow==",
+ "version": "3.972.8",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.8.tgz",
+ "integrity": "sha512-1eD4uhTDeambO/PNIDVG19A6+v4NdD7xzwLHDutHsUqz0B+i661MwQB2eYO4/crcCvCiQG4SRm1k81k54FEIvw==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/types": "^3.973.1",
- "@smithy/config-resolver": "^4.4.6",
- "@smithy/node-config-provider": "^4.3.8",
- "@smithy/types": "^4.12.0",
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/config-resolver": "^4.4.11",
+ "@smithy/node-config-provider": "^4.3.12",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -4867,17 +4425,17 @@
}
},
"node_modules/@aws-sdk/token-providers": {
- "version": "3.980.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.980.0.tgz",
- "integrity": "sha512-1nFileg1wAgDmieRoj9dOawgr2hhlh7xdvcH57b1NnqfPaVlcqVJyPc6k3TLDUFPY69eEwNxdGue/0wIz58vjA==",
+ "version": "3.1011.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1011.0.tgz",
+ "integrity": "sha512-WSfBVDQ9uyh1GCR+DxxgHEvAKv+beMIlSeJ2pMAG1HTci340+xbtz1VFwnTJ5qCxrMi+E4dyDMiSAhDvHnq73A==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.973.5",
- "@aws-sdk/nested-clients": "3.980.0",
- "@aws-sdk/types": "^3.973.1",
- "@smithy/property-provider": "^4.2.8",
- "@smithy/shared-ini-file-loader": "^4.4.3",
- "@smithy/types": "^4.12.0",
+ "@aws-sdk/core": "^3.973.20",
+ "@aws-sdk/nested-clients": "^3.996.10",
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/property-provider": "^4.2.12",
+ "@smithy/shared-ini-file-loader": "^4.4.7",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -4885,12 +4443,12 @@
}
},
"node_modules/@aws-sdk/types": {
- "version": "3.973.1",
- "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.1.tgz",
- "integrity": "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg==",
+ "version": "3.973.6",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.6.tgz",
+ "integrity": "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.12.0",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -4965,27 +4523,28 @@
}
},
"node_modules/@aws-sdk/util-user-agent-browser": {
- "version": "3.972.3",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.3.tgz",
- "integrity": "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw==",
+ "version": "3.972.8",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.8.tgz",
+ "integrity": "sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/types": "^3.973.1",
- "@smithy/types": "^4.12.0",
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/types": "^4.13.1",
"bowser": "^2.11.0",
"tslib": "^2.6.2"
}
},
"node_modules/@aws-sdk/util-user-agent-node": {
- "version": "3.972.4",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.4.tgz",
- "integrity": "sha512-3WFCBLiM8QiHDfosQq3Py+lIMgWlFWwFQliUHUqwEiRqLnKyhgbU3AKa7AWJF7lW2Oc/2kFNY4MlAYVnVc0i8A==",
+ "version": "3.973.7",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.7.tgz",
+ "integrity": "sha512-Hz6EZMUAEzqUd7e+vZ9LE7mn+5gMbxltXy18v+YSFY+9LBJz15wkNZvw5JqfX3z0FS9n3bgUtz3L5rAsfh4YlA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/middleware-user-agent": "^3.972.6",
- "@aws-sdk/types": "^3.973.1",
- "@smithy/node-config-provider": "^4.3.8",
- "@smithy/types": "^4.12.0",
+ "@aws-sdk/middleware-user-agent": "^3.972.21",
+ "@aws-sdk/types": "^3.973.6",
+ "@smithy/node-config-provider": "^4.3.12",
+ "@smithy/types": "^4.13.1",
+ "@smithy/util-config-provider": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -5001,13 +4560,13 @@
}
},
"node_modules/@aws-sdk/xml-builder": {
- "version": "3.972.4",
- "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.4.tgz",
- "integrity": "sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q==",
+ "version": "3.972.11",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.11.tgz",
+ "integrity": "sha512-iitV/gZKQMvY9d7ovmyFnFuTHbBAtrmLnvaSb/3X8vOKyevwtpmEtyc8AdhVWZe0pI/1GsHxlEvQeOePFzy7KQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.12.0",
- "fast-xml-parser": "5.3.8",
+ "@smithy/types": "^4.13.1",
+ "fast-xml-parser": "5.4.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -12324,9 +11883,9 @@
}
},
"node_modules/@librechat/agents": {
- "version": "3.1.56",
- "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-3.1.56.tgz",
- "integrity": "sha512-HJJwRnLM4XKpTWB4/wPDJR+iegyKBVUwqj7A8QHqzEcHzjKJDTr3wBPxZVH1tagGr6/mbbnErOJ14cH1OSNmpA==",
+ "version": "3.1.57",
+ "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-3.1.57.tgz",
+ "integrity": "sha512-fP/ZF7a7QL/MhXTfdzpG3cpOai9LSiKMiFX1X23o3t67Bqj9r5FuSVgu+UHDfO7o4Np82ZWw2nQJjcMJQbArLA==",
"license": "MIT",
"dependencies": {
"@anthropic-ai/sdk": "^0.73.0",
@@ -19584,12 +19143,12 @@
}
},
"node_modules/@smithy/abort-controller": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.8.tgz",
- "integrity": "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==",
+ "version": "4.2.12",
+ "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.12.tgz",
+ "integrity": "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.12.0",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -19622,16 +19181,16 @@
}
},
"node_modules/@smithy/config-resolver": {
- "version": "4.4.6",
- "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.6.tgz",
- "integrity": "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==",
+ "version": "4.4.11",
+ "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.11.tgz",
+ "integrity": "sha512-YxFiiG4YDAtX7WMN7RuhHZLeTmRRAOyCbr+zB8e3AQzHPnUhS8zXjB1+cniPVQI3xbWsQPM0X2aaIkO/ME0ymw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/node-config-provider": "^4.3.8",
- "@smithy/types": "^4.12.0",
- "@smithy/util-config-provider": "^4.2.0",
- "@smithy/util-endpoints": "^3.2.8",
- "@smithy/util-middleware": "^4.2.8",
+ "@smithy/node-config-provider": "^4.3.12",
+ "@smithy/types": "^4.13.1",
+ "@smithy/util-config-provider": "^4.2.2",
+ "@smithy/util-endpoints": "^3.3.3",
+ "@smithy/util-middleware": "^4.2.12",
"tslib": "^2.6.2"
},
"engines": {
@@ -19639,20 +19198,20 @@
}
},
"node_modules/@smithy/core": {
- "version": "3.22.0",
- "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.22.0.tgz",
- "integrity": "sha512-6vjCHD6vaY8KubeNw2Fg3EK0KLGQYdldG4fYgQmA0xSW0dJ8G2xFhSOdrlUakWVoP5JuWHtFODg3PNd/DN3FDA==",
+ "version": "3.23.12",
+ "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.12.tgz",
+ "integrity": "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/middleware-serde": "^4.2.9",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/types": "^4.12.0",
- "@smithy/util-base64": "^4.3.0",
- "@smithy/util-body-length-browser": "^4.2.0",
- "@smithy/util-middleware": "^4.2.8",
- "@smithy/util-stream": "^4.5.10",
- "@smithy/util-utf8": "^4.2.0",
- "@smithy/uuid": "^1.1.0",
+ "@smithy/protocol-http": "^5.3.12",
+ "@smithy/types": "^4.13.1",
+ "@smithy/url-parser": "^4.2.12",
+ "@smithy/util-base64": "^4.3.2",
+ "@smithy/util-body-length-browser": "^4.2.2",
+ "@smithy/util-middleware": "^4.2.12",
+ "@smithy/util-stream": "^4.5.20",
+ "@smithy/util-utf8": "^4.2.2",
+ "@smithy/uuid": "^1.1.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -19660,9 +19219,9 @@
}
},
"node_modules/@smithy/core/node_modules/@smithy/is-array-buffer": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz",
- "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz",
+ "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -19672,12 +19231,12 @@
}
},
"node_modules/@smithy/core/node_modules/@smithy/util-buffer-from": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz",
- "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz",
+ "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/is-array-buffer": "^4.2.0",
+ "@smithy/is-array-buffer": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -19685,12 +19244,12 @@
}
},
"node_modules/@smithy/core/node_modules/@smithy/util-utf8": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz",
- "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz",
+ "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-buffer-from": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -19698,15 +19257,15 @@
}
},
"node_modules/@smithy/credential-provider-imds": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.8.tgz",
- "integrity": "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==",
+ "version": "4.2.12",
+ "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.12.tgz",
+ "integrity": "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/node-config-provider": "^4.3.8",
- "@smithy/property-provider": "^4.2.8",
- "@smithy/types": "^4.12.0",
- "@smithy/url-parser": "^4.2.8",
+ "@smithy/node-config-provider": "^4.3.12",
+ "@smithy/property-provider": "^4.2.12",
+ "@smithy/types": "^4.13.1",
+ "@smithy/url-parser": "^4.2.12",
"tslib": "^2.6.2"
},
"engines": {
@@ -19714,14 +19273,14 @@
}
},
"node_modules/@smithy/eventstream-codec": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.8.tgz",
- "integrity": "sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw==",
+ "version": "4.2.12",
+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.12.tgz",
+ "integrity": "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/crc32": "5.2.0",
- "@smithy/types": "^4.12.0",
- "@smithy/util-hex-encoding": "^4.2.0",
+ "@smithy/types": "^4.13.1",
+ "@smithy/util-hex-encoding": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -19729,13 +19288,13 @@
}
},
"node_modules/@smithy/eventstream-serde-browser": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.8.tgz",
- "integrity": "sha512-MTfQT/CRQz5g24ayXdjg53V0mhucZth4PESoA5IhvaWVDTOQLfo8qI9vzqHcPsdd2v6sqfTYqF5L/l+pea5Uyw==",
+ "version": "4.2.12",
+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.12.tgz",
+ "integrity": "sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/eventstream-serde-universal": "^4.2.8",
- "@smithy/types": "^4.12.0",
+ "@smithy/eventstream-serde-universal": "^4.2.12",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -19743,12 +19302,12 @@
}
},
"node_modules/@smithy/eventstream-serde-config-resolver": {
- "version": "4.3.8",
- "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.8.tgz",
- "integrity": "sha512-ah12+luBiDGzBruhu3efNy1IlbwSEdNiw8fOZksoKoWW1ZHvO/04MQsdnws/9Aj+5b0YXSSN2JXKy/ClIsW8MQ==",
+ "version": "4.3.12",
+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.12.tgz",
+ "integrity": "sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.12.0",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -19756,13 +19315,13 @@
}
},
"node_modules/@smithy/eventstream-serde-node": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.8.tgz",
- "integrity": "sha512-cYpCpp29z6EJHa5T9WL0KAlq3SOKUQkcgSoeRfRVwjGgSFl7Uh32eYGt7IDYCX20skiEdRffyDpvF2efEZPC0A==",
+ "version": "4.2.12",
+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.12.tgz",
+ "integrity": "sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/eventstream-serde-universal": "^4.2.8",
- "@smithy/types": "^4.12.0",
+ "@smithy/eventstream-serde-universal": "^4.2.12",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -19770,13 +19329,13 @@
}
},
"node_modules/@smithy/eventstream-serde-universal": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.8.tgz",
- "integrity": "sha512-iJ6YNJd0bntJYnX6s52NC4WFYcZeKrPUr1Kmmr5AwZcwCSzVpS7oavAmxMR7pMq7V+D1G4s9F5NJK0xwOsKAlQ==",
+ "version": "4.2.12",
+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.12.tgz",
+ "integrity": "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/eventstream-codec": "^4.2.8",
- "@smithy/types": "^4.12.0",
+ "@smithy/eventstream-codec": "^4.2.12",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -19784,15 +19343,15 @@
}
},
"node_modules/@smithy/fetch-http-handler": {
- "version": "5.3.9",
- "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.9.tgz",
- "integrity": "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==",
+ "version": "5.3.15",
+ "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.15.tgz",
+ "integrity": "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/querystring-builder": "^4.2.8",
- "@smithy/types": "^4.12.0",
- "@smithy/util-base64": "^4.3.0",
+ "@smithy/protocol-http": "^5.3.12",
+ "@smithy/querystring-builder": "^4.2.12",
+ "@smithy/types": "^4.13.1",
+ "@smithy/util-base64": "^4.3.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -19815,14 +19374,14 @@
}
},
"node_modules/@smithy/hash-node": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.8.tgz",
- "integrity": "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==",
+ "version": "4.2.12",
+ "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.12.tgz",
+ "integrity": "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.12.0",
- "@smithy/util-buffer-from": "^4.2.0",
- "@smithy/util-utf8": "^4.2.0",
+ "@smithy/types": "^4.13.1",
+ "@smithy/util-buffer-from": "^4.2.2",
+ "@smithy/util-utf8": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -19830,9 +19389,9 @@
}
},
"node_modules/@smithy/hash-node/node_modules/@smithy/is-array-buffer": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz",
- "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz",
+ "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -19842,12 +19401,12 @@
}
},
"node_modules/@smithy/hash-node/node_modules/@smithy/util-buffer-from": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz",
- "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz",
+ "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/is-array-buffer": "^4.2.0",
+ "@smithy/is-array-buffer": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -19855,12 +19414,12 @@
}
},
"node_modules/@smithy/hash-node/node_modules/@smithy/util-utf8": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz",
- "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz",
+ "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-buffer-from": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -19920,12 +19479,12 @@
}
},
"node_modules/@smithy/invalid-dependency": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.8.tgz",
- "integrity": "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==",
+ "version": "4.2.12",
+ "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.12.tgz",
+ "integrity": "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.12.0",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -19996,13 +19555,13 @@
}
},
"node_modules/@smithy/middleware-content-length": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz",
- "integrity": "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==",
+ "version": "4.2.12",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.12.tgz",
+ "integrity": "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/types": "^4.12.0",
+ "@smithy/protocol-http": "^5.3.12",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -20010,18 +19569,18 @@
}
},
"node_modules/@smithy/middleware-endpoint": {
- "version": "4.4.12",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.12.tgz",
- "integrity": "sha512-9JMKHVJtW9RysTNjcBZQHDwB0p3iTP6B1IfQV4m+uCevkVd/VuLgwfqk5cnI4RHcp4cPwoIvxQqN4B1sxeHo8Q==",
+ "version": "4.4.26",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.26.tgz",
+ "integrity": "sha512-8Qfikvd2GVKSm8S6IbjfwFlRY9VlMrj0Dp4vTwAuhqbX7NhJKE5DQc2bnfJIcY0B+2YKMDBWfvexbSZeejDgeg==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/core": "^3.22.0",
- "@smithy/middleware-serde": "^4.2.9",
- "@smithy/node-config-provider": "^4.3.8",
- "@smithy/shared-ini-file-loader": "^4.4.3",
- "@smithy/types": "^4.12.0",
- "@smithy/url-parser": "^4.2.8",
- "@smithy/util-middleware": "^4.2.8",
+ "@smithy/core": "^3.23.12",
+ "@smithy/middleware-serde": "^4.2.15",
+ "@smithy/node-config-provider": "^4.3.12",
+ "@smithy/shared-ini-file-loader": "^4.4.7",
+ "@smithy/types": "^4.13.1",
+ "@smithy/url-parser": "^4.2.12",
+ "@smithy/util-middleware": "^4.2.12",
"tslib": "^2.6.2"
},
"engines": {
@@ -20029,19 +19588,19 @@
}
},
"node_modules/@smithy/middleware-retry": {
- "version": "4.4.29",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.29.tgz",
- "integrity": "sha512-bmTn75a4tmKRkC5w61yYQLb3DmxNzB8qSVu9SbTYqW6GAL0WXO2bDZuMAn/GJSbOdHEdjZvWxe+9Kk015bw6Cg==",
+ "version": "4.4.43",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.43.tgz",
+ "integrity": "sha512-ZwsifBdyuNHrFGmbc7bAfP2b54+kt9J2rhFd18ilQGAB+GDiP4SrawqyExbB7v455QVR7Psyhb2kjULvBPIhvA==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/node-config-provider": "^4.3.8",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/service-error-classification": "^4.2.8",
- "@smithy/smithy-client": "^4.11.1",
- "@smithy/types": "^4.12.0",
- "@smithy/util-middleware": "^4.2.8",
- "@smithy/util-retry": "^4.2.8",
- "@smithy/uuid": "^1.1.0",
+ "@smithy/node-config-provider": "^4.3.12",
+ "@smithy/protocol-http": "^5.3.12",
+ "@smithy/service-error-classification": "^4.2.12",
+ "@smithy/smithy-client": "^4.12.6",
+ "@smithy/types": "^4.13.1",
+ "@smithy/util-middleware": "^4.2.12",
+ "@smithy/util-retry": "^4.2.12",
+ "@smithy/uuid": "^1.1.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -20049,13 +19608,14 @@
}
},
"node_modules/@smithy/middleware-serde": {
- "version": "4.2.9",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.9.tgz",
- "integrity": "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==",
+ "version": "4.2.15",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.15.tgz",
+ "integrity": "sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/types": "^4.12.0",
+ "@smithy/core": "^3.23.12",
+ "@smithy/protocol-http": "^5.3.12",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -20063,12 +19623,12 @@
}
},
"node_modules/@smithy/middleware-stack": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.8.tgz",
- "integrity": "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==",
+ "version": "4.2.12",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.12.tgz",
+ "integrity": "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.12.0",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -20076,14 +19636,14 @@
}
},
"node_modules/@smithy/node-config-provider": {
- "version": "4.3.8",
- "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.8.tgz",
- "integrity": "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==",
+ "version": "4.3.12",
+ "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.12.tgz",
+ "integrity": "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/property-provider": "^4.2.8",
- "@smithy/shared-ini-file-loader": "^4.4.3",
- "@smithy/types": "^4.12.0",
+ "@smithy/property-provider": "^4.2.12",
+ "@smithy/shared-ini-file-loader": "^4.4.7",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -20091,15 +19651,15 @@
}
},
"node_modules/@smithy/node-http-handler": {
- "version": "4.4.8",
- "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.8.tgz",
- "integrity": "sha512-q9u+MSbJVIJ1QmJ4+1u+cERXkrhuILCBDsJUBAW1MPE6sFonbCNaegFuwW9ll8kh5UdyY3jOkoOGlc7BesoLpg==",
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.0.tgz",
+ "integrity": "sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/abort-controller": "^4.2.8",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/querystring-builder": "^4.2.8",
- "@smithy/types": "^4.12.0",
+ "@smithy/abort-controller": "^4.2.12",
+ "@smithy/protocol-http": "^5.3.12",
+ "@smithy/querystring-builder": "^4.2.12",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -20107,12 +19667,12 @@
}
},
"node_modules/@smithy/property-provider": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.8.tgz",
- "integrity": "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==",
+ "version": "4.2.12",
+ "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.12.tgz",
+ "integrity": "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.12.0",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -20120,12 +19680,12 @@
}
},
"node_modules/@smithy/protocol-http": {
- "version": "5.3.8",
- "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.8.tgz",
- "integrity": "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==",
+ "version": "5.3.12",
+ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz",
+ "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.12.0",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -20133,13 +19693,13 @@
}
},
"node_modules/@smithy/querystring-builder": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.8.tgz",
- "integrity": "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==",
+ "version": "4.2.12",
+ "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz",
+ "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.12.0",
- "@smithy/util-uri-escape": "^4.2.0",
+ "@smithy/types": "^4.13.1",
+ "@smithy/util-uri-escape": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -20147,12 +19707,12 @@
}
},
"node_modules/@smithy/querystring-parser": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.8.tgz",
- "integrity": "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==",
+ "version": "4.2.12",
+ "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.12.tgz",
+ "integrity": "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.12.0",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -20160,24 +19720,24 @@
}
},
"node_modules/@smithy/service-error-classification": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.8.tgz",
- "integrity": "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==",
+ "version": "4.2.12",
+ "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.12.tgz",
+ "integrity": "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.12.0"
+ "@smithy/types": "^4.13.1"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@smithy/shared-ini-file-loader": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.3.tgz",
- "integrity": "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==",
+ "version": "4.4.7",
+ "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.7.tgz",
+ "integrity": "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.12.0",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -20185,18 +19745,18 @@
}
},
"node_modules/@smithy/signature-v4": {
- "version": "5.3.8",
- "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.8.tgz",
- "integrity": "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==",
+ "version": "5.3.12",
+ "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.12.tgz",
+ "integrity": "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/is-array-buffer": "^4.2.0",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/types": "^4.12.0",
- "@smithy/util-hex-encoding": "^4.2.0",
- "@smithy/util-middleware": "^4.2.8",
- "@smithy/util-uri-escape": "^4.2.0",
- "@smithy/util-utf8": "^4.2.0",
+ "@smithy/is-array-buffer": "^4.2.2",
+ "@smithy/protocol-http": "^5.3.12",
+ "@smithy/types": "^4.13.1",
+ "@smithy/util-hex-encoding": "^4.2.2",
+ "@smithy/util-middleware": "^4.2.12",
+ "@smithy/util-uri-escape": "^4.2.2",
+ "@smithy/util-utf8": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -20204,9 +19764,9 @@
}
},
"node_modules/@smithy/signature-v4/node_modules/@smithy/is-array-buffer": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz",
- "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz",
+ "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -20216,12 +19776,12 @@
}
},
"node_modules/@smithy/signature-v4/node_modules/@smithy/util-buffer-from": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz",
- "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz",
+ "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/is-array-buffer": "^4.2.0",
+ "@smithy/is-array-buffer": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -20229,12 +19789,12 @@
}
},
"node_modules/@smithy/signature-v4/node_modules/@smithy/util-utf8": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz",
- "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz",
+ "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-buffer-from": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -20242,17 +19802,17 @@
}
},
"node_modules/@smithy/smithy-client": {
- "version": "4.11.1",
- "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.1.tgz",
- "integrity": "sha512-SERgNg5Z1U+jfR6/2xPYjSEHY1t3pyTHC/Ma3YQl6qWtmiL42bvNId3W/oMUWIwu7ekL2FMPdqAmwbQegM7HeQ==",
+ "version": "4.12.6",
+ "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.6.tgz",
+ "integrity": "sha512-aib3f0jiMsJ6+cvDnXipBsGDL7ztknYSVqJs1FdN9P+u9tr/VzOR7iygSh6EUOdaBeMCMSh3N0VdyYsG4o91DQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/core": "^3.22.0",
- "@smithy/middleware-endpoint": "^4.4.12",
- "@smithy/middleware-stack": "^4.2.8",
- "@smithy/protocol-http": "^5.3.8",
- "@smithy/types": "^4.12.0",
- "@smithy/util-stream": "^4.5.10",
+ "@smithy/core": "^3.23.12",
+ "@smithy/middleware-endpoint": "^4.4.26",
+ "@smithy/middleware-stack": "^4.2.12",
+ "@smithy/protocol-http": "^5.3.12",
+ "@smithy/types": "^4.13.1",
+ "@smithy/util-stream": "^4.5.20",
"tslib": "^2.6.2"
},
"engines": {
@@ -20260,9 +19820,9 @@
}
},
"node_modules/@smithy/types": {
- "version": "4.12.0",
- "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz",
- "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==",
+ "version": "4.13.1",
+ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz",
+ "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -20272,13 +19832,13 @@
}
},
"node_modules/@smithy/url-parser": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.8.tgz",
- "integrity": "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==",
+ "version": "4.2.12",
+ "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.12.tgz",
+ "integrity": "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/querystring-parser": "^4.2.8",
- "@smithy/types": "^4.12.0",
+ "@smithy/querystring-parser": "^4.2.12",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -20286,13 +19846,13 @@
}
},
"node_modules/@smithy/util-base64": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz",
- "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==",
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz",
+ "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/util-buffer-from": "^4.2.0",
- "@smithy/util-utf8": "^4.2.0",
+ "@smithy/util-buffer-from": "^4.2.2",
+ "@smithy/util-utf8": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -20300,9 +19860,9 @@
}
},
"node_modules/@smithy/util-base64/node_modules/@smithy/is-array-buffer": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz",
- "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz",
+ "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -20312,12 +19872,12 @@
}
},
"node_modules/@smithy/util-base64/node_modules/@smithy/util-buffer-from": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz",
- "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz",
+ "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/is-array-buffer": "^4.2.0",
+ "@smithy/is-array-buffer": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -20325,12 +19885,12 @@
}
},
"node_modules/@smithy/util-base64/node_modules/@smithy/util-utf8": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz",
- "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz",
+ "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-buffer-from": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -20338,9 +19898,9 @@
}
},
"node_modules/@smithy/util-body-length-browser": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz",
- "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz",
+ "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -20350,9 +19910,9 @@
}
},
"node_modules/@smithy/util-body-length-node": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz",
- "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz",
+ "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -20374,9 +19934,9 @@
}
},
"node_modules/@smithy/util-config-provider": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz",
- "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz",
+ "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -20386,14 +19946,14 @@
}
},
"node_modules/@smithy/util-defaults-mode-browser": {
- "version": "4.3.28",
- "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.28.tgz",
- "integrity": "sha512-/9zcatsCao9h6g18p/9vH9NIi5PSqhCkxQ/tb7pMgRFnqYp9XUOyOlGPDMHzr8n5ih6yYgwJEY2MLEobUgi47w==",
+ "version": "4.3.42",
+ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.42.tgz",
+ "integrity": "sha512-0vjwmcvkWAUtikXnWIUOyV6IFHTEeQUYh3JUZcDgcszF+hD/StAsQ3rCZNZEPHgI9kVNcbnyc8P2CBHnwgmcwg==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/property-provider": "^4.2.8",
- "@smithy/smithy-client": "^4.11.1",
- "@smithy/types": "^4.12.0",
+ "@smithy/property-provider": "^4.2.12",
+ "@smithy/smithy-client": "^4.12.6",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -20401,17 +19961,17 @@
}
},
"node_modules/@smithy/util-defaults-mode-node": {
- "version": "4.2.31",
- "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.31.tgz",
- "integrity": "sha512-JTvoApUXA5kbpceI2vuqQzRjeTbLpx1eoa5R/YEZbTgtxvIB7AQZxFJ0SEyfCpgPCyVV9IT7we+ytSeIB3CyWA==",
+ "version": "4.2.45",
+ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.45.tgz",
+ "integrity": "sha512-q5dOqqfTgUcLe38TAGiFn9srToKj2YCHJ34QGOLzM+xYLLA+qRZv7N+33kl1MERVusue36ZHnlNaNEvY/PzSrw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/config-resolver": "^4.4.6",
- "@smithy/credential-provider-imds": "^4.2.8",
- "@smithy/node-config-provider": "^4.3.8",
- "@smithy/property-provider": "^4.2.8",
- "@smithy/smithy-client": "^4.11.1",
- "@smithy/types": "^4.12.0",
+ "@smithy/config-resolver": "^4.4.11",
+ "@smithy/credential-provider-imds": "^4.2.12",
+ "@smithy/node-config-provider": "^4.3.12",
+ "@smithy/property-provider": "^4.2.12",
+ "@smithy/smithy-client": "^4.12.6",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -20419,13 +19979,13 @@
}
},
"node_modules/@smithy/util-endpoints": {
- "version": "3.2.8",
- "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.8.tgz",
- "integrity": "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==",
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.3.tgz",
+ "integrity": "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/node-config-provider": "^4.3.8",
- "@smithy/types": "^4.12.0",
+ "@smithy/node-config-provider": "^4.3.12",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -20433,9 +19993,9 @@
}
},
"node_modules/@smithy/util-hex-encoding": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz",
- "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz",
+ "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -20445,12 +20005,12 @@
}
},
"node_modules/@smithy/util-middleware": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.8.tgz",
- "integrity": "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==",
+ "version": "4.2.12",
+ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz",
+ "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.12.0",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -20458,13 +20018,13 @@
}
},
"node_modules/@smithy/util-retry": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.8.tgz",
- "integrity": "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==",
+ "version": "4.2.12",
+ "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.12.tgz",
+ "integrity": "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/service-error-classification": "^4.2.8",
- "@smithy/types": "^4.12.0",
+ "@smithy/service-error-classification": "^4.2.12",
+ "@smithy/types": "^4.13.1",
"tslib": "^2.6.2"
},
"engines": {
@@ -20472,18 +20032,18 @@
}
},
"node_modules/@smithy/util-stream": {
- "version": "4.5.10",
- "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.10.tgz",
- "integrity": "sha512-jbqemy51UFSZSp2y0ZmRfckmrzuKww95zT9BYMmuJ8v3altGcqjwoV1tzpOwuHaKrwQrCjIzOib499ymr2f98g==",
+ "version": "4.5.20",
+ "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.20.tgz",
+ "integrity": "sha512-4yXLm5n/B5SRBR2p8cZ90Sbv4zL4NKsgxdzCzp/83cXw2KxLEumt5p+GAVyRNZgQOSrzXn9ARpO0lUe8XSlSDw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/fetch-http-handler": "^5.3.9",
- "@smithy/node-http-handler": "^4.4.8",
- "@smithy/types": "^4.12.0",
- "@smithy/util-base64": "^4.3.0",
- "@smithy/util-buffer-from": "^4.2.0",
- "@smithy/util-hex-encoding": "^4.2.0",
- "@smithy/util-utf8": "^4.2.0",
+ "@smithy/fetch-http-handler": "^5.3.15",
+ "@smithy/node-http-handler": "^4.5.0",
+ "@smithy/types": "^4.13.1",
+ "@smithy/util-base64": "^4.3.2",
+ "@smithy/util-buffer-from": "^4.2.2",
+ "@smithy/util-hex-encoding": "^4.2.2",
+ "@smithy/util-utf8": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -20491,9 +20051,9 @@
}
},
"node_modules/@smithy/util-stream/node_modules/@smithy/is-array-buffer": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz",
- "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz",
+ "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -20503,12 +20063,12 @@
}
},
"node_modules/@smithy/util-stream/node_modules/@smithy/util-buffer-from": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz",
- "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz",
+ "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/is-array-buffer": "^4.2.0",
+ "@smithy/is-array-buffer": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -20516,12 +20076,12 @@
}
},
"node_modules/@smithy/util-stream/node_modules/@smithy/util-utf8": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz",
- "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz",
+ "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-buffer-from": "^4.2.2",
"tslib": "^2.6.2"
},
"engines": {
@@ -20529,9 +20089,9 @@
}
},
"node_modules/@smithy/util-uri-escape": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz",
- "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz",
+ "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -20567,9 +20127,9 @@
}
},
"node_modules/@smithy/uuid": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz",
- "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==",
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz",
+ "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -27504,10 +27064,10 @@
],
"license": "BSD-3-Clause"
},
- "node_modules/fast-xml-parser": {
- "version": "5.3.8",
- "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.8.tgz",
- "integrity": "sha512-53jIF4N6u/pxvaL1eb/hEZts/cFLWZ92eCfLrNyCI0k38lettCG/Bs40W9pPwoPXyHQlKu2OUbQtiEIZK/J6Vw==",
+ "node_modules/fast-xml-builder": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz",
+ "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==",
"funding": [
{
"type": "github",
@@ -27516,7 +27076,24 @@
],
"license": "MIT",
"dependencies": {
- "strnum": "^2.1.0"
+ "path-expression-matcher": "^1.1.3"
+ }
+ },
+ "node_modules/fast-xml-parser": {
+ "version": "5.5.6",
+ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.6.tgz",
+ "integrity": "sha512-3+fdZyBRVg29n4rXP0joHthhcHdPUHaIC16cuyyd1iLsuaO6Vea36MPrxgAzbZna8lhvZeRL8Bc9GP56/J9xEw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/NaturalIntelligence"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "fast-xml-builder": "^1.1.4",
+ "path-expression-matcher": "^1.1.3",
+ "strnum": "^2.1.2"
},
"bin": {
"fxparser": "src/cli/cli.js"
@@ -27813,9 +27390,9 @@
}
},
"node_modules/flatted": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
- "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"dev": true,
"license": "ISC"
},
@@ -35689,6 +35266,21 @@
"node": ">=8"
}
},
+ "node_modules/path-expression-matcher": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz",
+ "integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/NaturalIntelligence"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -44240,7 +43832,7 @@
"@google/genai": "^1.19.0",
"@keyv/redis": "^4.3.3",
"@langchain/core": "^0.3.80",
- "@librechat/agents": "^3.1.56",
+ "@librechat/agents": "^3.1.57",
"@librechat/data-schemas": "*",
"@modelcontextprotocol/sdk": "^1.27.1",
"@smithy/node-http-handler": "^4.4.5",
diff --git a/package.json b/package.json
index 6605752f39..e59032c7dd 100644
--- a/package.json
+++ b/package.json
@@ -139,14 +139,13 @@
"@librechat/agents": {
"@langchain/anthropic": {
"@anthropic-ai/sdk": "0.73.0",
- "fast-xml-parser": "5.3.8"
+ "fast-xml-parser": "5.5.6"
},
"@anthropic-ai/sdk": "0.73.0",
- "fast-xml-parser": "5.3.8"
+ "fast-xml-parser": "5.5.6"
},
- "axios": "1.12.1",
"elliptic": "^6.6.1",
- "fast-xml-parser": "5.3.8",
+ "fast-xml-parser": "5.5.6",
"form-data": "^4.0.4",
"tslib": "^2.8.1",
"mdast-util-gfm-autolink-literal": "2.0.0",
diff --git a/packages/api/package.json b/packages/api/package.json
index 46fbeb02b6..42f1f0e9f0 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -90,7 +90,7 @@
"@google/genai": "^1.19.0",
"@keyv/redis": "^4.3.3",
"@langchain/core": "^0.3.80",
- "@librechat/agents": "^3.1.56",
+ "@librechat/agents": "^3.1.57",
"@librechat/data-schemas": "*",
"@modelcontextprotocol/sdk": "^1.27.1",
"@smithy/node-http-handler": "^4.4.5",
From 9cb5ac63f89210dc506f7cdd64f127265629d781 Mon Sep 17 00:00:00 2001
From: Danny Avila
Date: Thu, 19 Mar 2026 14:51:28 -0400
Subject: [PATCH 03/98] =?UTF-8?q?=F0=9F=AB=A7=20refactor:=20Clear=20Drafts?=
=?UTF-8?q?=20and=20Surface=20Error=20on=20Expired=20SSE=20Stream=20(#1230?=
=?UTF-8?q?9)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* refactor: error handling in useResumableSSE for 404 responses
- Added logic to clear drafts from localStorage when a 404 error occurs.
- Integrated errorHandler to notify users of the error condition.
- Introduced comprehensive tests to validate the new behavior, ensuring drafts are cleared and error handling is triggered correctly.C
* feat: add STREAM_EXPIRED error handling and message localization
- Introduced handling for STREAM_EXPIRED errors in useResumableSSE, updating errorHandler to provide relevant feedback.
- Added a new error message for STREAM_EXPIRED in translation files for user notifications.
- Updated tests to ensure proper error handling and message verification for STREAM_EXPIRED scenarios.
* refactor: replace clearDraft with clearAllDrafts utility
- Removed the clearDraft function from useResumableSSE and useSSE hooks, replacing it with the new clearAllDrafts utility for better draft management.
- Updated localStorage interactions to ensure both text and file drafts are cleared consistently for a conversation.
- Enhanced code readability and maintainability by centralizing draft clearing logic.
---
.../src/components/Messages/Content/Error.tsx | 1 +
.../SSE/__tests__/useResumableSSE.spec.ts | 273 ++++++++++++++++++
client/src/hooks/SSE/useResumableSSE.ts | 22 +-
client/src/hooks/SSE/useSSE.ts | 22 +-
client/src/locales/en/translation.json | 1 +
client/src/utils/drafts.ts | 9 +-
packages/data-provider/src/config.ts | 4 +
7 files changed, 299 insertions(+), 33 deletions(-)
create mode 100644 client/src/hooks/SSE/__tests__/useResumableSSE.spec.ts
diff --git a/client/src/components/Messages/Content/Error.tsx b/client/src/components/Messages/Content/Error.tsx
index ff2f2d7e90..b464ce2f2a 100644
--- a/client/src/components/Messages/Content/Error.tsx
+++ b/client/src/components/Messages/Content/Error.tsx
@@ -75,6 +75,7 @@ const errorMessages = {
return info;
},
[ErrorTypes.GOOGLE_TOOL_CONFLICT]: 'com_error_google_tool_conflict',
+ [ErrorTypes.STREAM_EXPIRED]: 'com_error_stream_expired',
[ViolationTypes.BAN]:
'Your account has been temporarily banned due to violations of our service.',
[ViolationTypes.ILLEGAL_MODEL_REQUEST]: (json: TGenericError, localize: LocalizeFunction) => {
diff --git a/client/src/hooks/SSE/__tests__/useResumableSSE.spec.ts b/client/src/hooks/SSE/__tests__/useResumableSSE.spec.ts
new file mode 100644
index 0000000000..9100f39858
--- /dev/null
+++ b/client/src/hooks/SSE/__tests__/useResumableSSE.spec.ts
@@ -0,0 +1,273 @@
+import { renderHook, act } from '@testing-library/react';
+import { Constants, ErrorTypes, LocalStorageKeys } from 'librechat-data-provider';
+import type { TSubmission } from 'librechat-data-provider';
+
+type SSEEventListener = (e: Partial & { responseCode?: number }) => void;
+
+interface MockSSEInstance {
+ addEventListener: jest.Mock;
+ stream: jest.Mock;
+ close: jest.Mock;
+ headers: Record;
+ _listeners: Record;
+ _emit: (event: string, data?: Partial & { responseCode?: number }) => void;
+}
+
+const mockSSEInstances: MockSSEInstance[] = [];
+
+jest.mock('sse.js', () => ({
+ SSE: jest.fn().mockImplementation(() => {
+ const listeners: Record = {};
+ const instance: MockSSEInstance = {
+ addEventListener: jest.fn((event: string, cb: SSEEventListener) => {
+ listeners[event] = cb;
+ }),
+ stream: jest.fn(),
+ close: jest.fn(),
+ headers: {},
+ _listeners: listeners,
+ _emit: (event, data = {}) => listeners[event]?.(data as MessageEvent),
+ };
+ mockSSEInstances.push(instance);
+ return instance;
+ }),
+}));
+
+const mockSetQueryData = jest.fn();
+const mockQueryClient = { setQueryData: mockSetQueryData };
+
+jest.mock('@tanstack/react-query', () => ({
+ ...jest.requireActual('@tanstack/react-query'),
+ useQueryClient: () => mockQueryClient,
+}));
+
+jest.mock('recoil', () => ({
+ ...jest.requireActual('recoil'),
+ useSetRecoilState: () => jest.fn(),
+}));
+
+jest.mock('~/store', () => ({
+ __esModule: true,
+ default: {
+ activeRunFamily: jest.fn(),
+ abortScrollFamily: jest.fn(),
+ showStopButtonByIndex: jest.fn(),
+ },
+}));
+
+jest.mock('~/hooks/AuthContext', () => ({
+ useAuthContext: () => ({ token: 'test-token', isAuthenticated: true }),
+}));
+
+jest.mock('~/data-provider', () => ({
+ useGetStartupConfig: () => ({ data: { balance: { enabled: false } } }),
+ useGetUserBalance: () => ({ refetch: jest.fn() }),
+ queueTitleGeneration: jest.fn(),
+}));
+
+const mockErrorHandler = jest.fn();
+const mockSetIsSubmitting = jest.fn();
+const mockClearStepMaps = jest.fn();
+
+jest.mock('~/hooks/SSE/useEventHandlers', () =>
+ jest.fn(() => ({
+ errorHandler: mockErrorHandler,
+ finalHandler: jest.fn(),
+ createdHandler: jest.fn(),
+ attachmentHandler: jest.fn(),
+ stepHandler: jest.fn(),
+ contentHandler: jest.fn(),
+ resetContentHandler: jest.fn(),
+ syncStepMessage: jest.fn(),
+ clearStepMaps: mockClearStepMaps,
+ messageHandler: jest.fn(),
+ setIsSubmitting: mockSetIsSubmitting,
+ setShowStopButton: jest.fn(),
+ })),
+);
+
+jest.mock('librechat-data-provider', () => {
+ const actual = jest.requireActual('librechat-data-provider');
+ return {
+ ...actual,
+ createPayload: jest.fn(() => ({
+ payload: { model: 'gpt-4o' },
+ server: '/api/agents/chat',
+ })),
+ removeNullishValues: jest.fn((v: unknown) => v),
+ apiBaseUrl: jest.fn(() => ''),
+ request: {
+ post: jest.fn().mockResolvedValue({ streamId: 'stream-123' }),
+ refreshToken: jest.fn(),
+ dispatchTokenUpdatedEvent: jest.fn(),
+ },
+ };
+});
+
+import useResumableSSE from '~/hooks/SSE/useResumableSSE';
+
+const CONV_ID = 'conv-abc-123';
+
+type PartialSubmission = {
+ conversation: { conversationId?: string };
+ userMessage: Record;
+ messages: never[];
+ isTemporary: boolean;
+ initialResponse: Record;
+ endpointOption: { endpoint: string };
+};
+
+const buildSubmission = (overrides: Partial = {}): TSubmission => {
+ const conversationId = overrides.conversation?.conversationId ?? CONV_ID;
+ return {
+ conversation: { conversationId },
+ userMessage: {
+ messageId: 'msg-1',
+ conversationId,
+ text: 'Hello',
+ isCreatedByUser: true,
+ sender: 'User',
+ parentMessageId: '00000000-0000-0000-0000-000000000000',
+ },
+ messages: [],
+ isTemporary: false,
+ initialResponse: {
+ messageId: 'resp-1',
+ conversationId,
+ text: '',
+ isCreatedByUser: false,
+ sender: 'Assistant',
+ },
+ endpointOption: { endpoint: 'agents' },
+ ...overrides,
+ } as unknown as TSubmission;
+};
+
+const buildChatHelpers = () => ({
+ setMessages: jest.fn(),
+ getMessages: jest.fn(() => []),
+ setConversation: jest.fn(),
+ setIsSubmitting: mockSetIsSubmitting,
+ newConversation: jest.fn(),
+ resetLatestMessage: jest.fn(),
+});
+
+const getLastSSE = (): MockSSEInstance => {
+ const sse = mockSSEInstances[mockSSEInstances.length - 1];
+ expect(sse).toBeDefined();
+ return sse;
+};
+
+describe('useResumableSSE - 404 error path', () => {
+ beforeEach(() => {
+ mockSSEInstances.length = 0;
+ localStorage.clear();
+ });
+
+ const seedDraft = (conversationId: string) => {
+ localStorage.setItem(`${LocalStorageKeys.TEXT_DRAFT}${conversationId}`, 'draft text');
+ localStorage.setItem(`${LocalStorageKeys.FILES_DRAFT}${conversationId}`, '[]');
+ };
+
+ const render404Scenario = async (conversationId = CONV_ID) => {
+ const submission = buildSubmission({ conversation: { conversationId } });
+ const chatHelpers = buildChatHelpers();
+
+ const { unmount } = renderHook(() => useResumableSSE(submission, chatHelpers));
+
+ await act(async () => {
+ await Promise.resolve();
+ });
+
+ const sse = getLastSSE();
+
+ await act(async () => {
+ sse._emit('error', { responseCode: 404 });
+ });
+
+ return { sse, unmount, chatHelpers };
+ };
+
+ it('clears the text and files draft from localStorage on 404', async () => {
+ seedDraft(CONV_ID);
+ expect(localStorage.getItem(`${LocalStorageKeys.TEXT_DRAFT}${CONV_ID}`)).not.toBeNull();
+ expect(localStorage.getItem(`${LocalStorageKeys.FILES_DRAFT}${CONV_ID}`)).not.toBeNull();
+
+ const { unmount } = await render404Scenario(CONV_ID);
+
+ expect(localStorage.getItem(`${LocalStorageKeys.TEXT_DRAFT}${CONV_ID}`)).toBeNull();
+ expect(localStorage.getItem(`${LocalStorageKeys.FILES_DRAFT}${CONV_ID}`)).toBeNull();
+ unmount();
+ });
+
+ it('calls errorHandler with STREAM_EXPIRED error type on 404', async () => {
+ const { unmount } = await render404Scenario(CONV_ID);
+
+ expect(mockErrorHandler).toHaveBeenCalledTimes(1);
+ const call = mockErrorHandler.mock.calls[0][0];
+ expect(call.data).toBeDefined();
+ const parsed = JSON.parse(call.data.text);
+ expect(parsed.type).toBe(ErrorTypes.STREAM_EXPIRED);
+ expect(call.submission).toEqual(
+ expect.objectContaining({
+ conversation: expect.objectContaining({ conversationId: CONV_ID }),
+ }),
+ );
+ unmount();
+ });
+
+ it('clears both TEXT and FILES drafts for new-convo when conversationId is absent', async () => {
+ localStorage.setItem(`${LocalStorageKeys.TEXT_DRAFT}${Constants.NEW_CONVO}`, 'unsent message');
+ localStorage.setItem(`${LocalStorageKeys.FILES_DRAFT}${Constants.NEW_CONVO}`, '[]');
+
+ const submission = buildSubmission({ conversation: {} });
+ const chatHelpers = buildChatHelpers();
+
+ const { unmount } = renderHook(() => useResumableSSE(submission, chatHelpers));
+
+ await act(async () => {
+ await Promise.resolve();
+ });
+
+ const sse = getLastSSE();
+ await act(async () => {
+ sse._emit('error', { responseCode: 404 });
+ });
+
+ expect(localStorage.getItem(`${LocalStorageKeys.TEXT_DRAFT}${Constants.NEW_CONVO}`)).toBeNull();
+ expect(
+ localStorage.getItem(`${LocalStorageKeys.FILES_DRAFT}${Constants.NEW_CONVO}`),
+ ).toBeNull();
+ unmount();
+ });
+
+ it('closes the SSE connection on 404', async () => {
+ const { sse, unmount } = await render404Scenario();
+
+ expect(sse.close).toHaveBeenCalled();
+ unmount();
+ });
+
+ it.each([undefined, 500, 503])(
+ 'does not call errorHandler for responseCode %s (reconnect path)',
+ async (responseCode) => {
+ const submission = buildSubmission();
+ const chatHelpers = buildChatHelpers();
+
+ const { unmount } = renderHook(() => useResumableSSE(submission, chatHelpers));
+
+ await act(async () => {
+ await Promise.resolve();
+ });
+
+ const sse = getLastSSE();
+
+ await act(async () => {
+ sse._emit('error', { responseCode });
+ });
+
+ expect(mockErrorHandler).not.toHaveBeenCalled();
+ unmount();
+ },
+ );
+});
diff --git a/client/src/hooks/SSE/useResumableSSE.ts b/client/src/hooks/SSE/useResumableSSE.ts
index 4d4cb4841a..ddfee30120 100644
--- a/client/src/hooks/SSE/useResumableSSE.ts
+++ b/client/src/hooks/SSE/useResumableSSE.ts
@@ -11,7 +11,6 @@ import {
apiBaseUrl,
createPayload,
ViolationTypes,
- LocalStorageKeys,
removeNullishValues,
} from 'librechat-data-provider';
import type { TMessage, TPayload, TSubmission, EventSubmission } from 'librechat-data-provider';
@@ -20,18 +19,9 @@ import { useGetStartupConfig, useGetUserBalance, queueTitleGeneration } from '~/
import type { ActiveJobsResponse } from '~/data-provider';
import { useAuthContext } from '~/hooks/AuthContext';
import useEventHandlers from './useEventHandlers';
+import { clearAllDrafts } from '~/utils';
import store from '~/store';
-const clearDraft = (conversationId?: string | null) => {
- if (conversationId) {
- localStorage.removeItem(`${LocalStorageKeys.TEXT_DRAFT}${conversationId}`);
- localStorage.removeItem(`${LocalStorageKeys.FILES_DRAFT}${conversationId}`);
- } else {
- localStorage.removeItem(`${LocalStorageKeys.TEXT_DRAFT}${Constants.NEW_CONVO}`);
- localStorage.removeItem(`${LocalStorageKeys.FILES_DRAFT}${Constants.NEW_CONVO}`);
- }
-};
-
type ChatHelpers = Pick<
EventHandlerParams,
| 'setMessages'
@@ -176,7 +166,7 @@ export default function useResumableSSE(
conversationId: data.conversation?.conversationId,
hasResponseMessage: !!data.responseMessage,
});
- clearDraft(currentSubmission.conversation?.conversationId);
+ clearAllDrafts(currentSubmission.conversation?.conversationId);
try {
finalHandler(data, currentSubmission as EventSubmission);
} catch (error) {
@@ -357,7 +347,13 @@ export default function useResumableSSE(
console.log('[ResumableSSE] Stream not found (404) - job completed or expired');
sse.close();
removeActiveJob(currentStreamId);
- setIsSubmitting(false);
+ clearAllDrafts(currentSubmission.conversation?.conversationId);
+ errorHandler({
+ data: {
+ text: JSON.stringify({ type: ErrorTypes.STREAM_EXPIRED }),
+ } as unknown as Parameters[0]['data'],
+ submission: currentSubmission as EventSubmission,
+ });
setShowStopButton(false);
setStreamId(null);
reconnectAttemptRef.current = 0;
diff --git a/client/src/hooks/SSE/useSSE.ts b/client/src/hooks/SSE/useSSE.ts
index ccdb252287..78835f5729 100644
--- a/client/src/hooks/SSE/useSSE.ts
+++ b/client/src/hooks/SSE/useSSE.ts
@@ -2,32 +2,16 @@ import { useEffect, useState } from 'react';
import { v4 } from 'uuid';
import { SSE } from 'sse.js';
import { useSetRecoilState } from 'recoil';
-import {
- request,
- Constants,
- /* @ts-ignore */
- createPayload,
- LocalStorageKeys,
- removeNullishValues,
-} from 'librechat-data-provider';
+import { request, createPayload, removeNullishValues } from 'librechat-data-provider';
import type { TMessage, TPayload, TSubmission, EventSubmission } from 'librechat-data-provider';
import type { EventHandlerParams } from './useEventHandlers';
import type { TResData } from '~/common';
import { useGetStartupConfig, useGetUserBalance } from '~/data-provider';
import { useAuthContext } from '~/hooks/AuthContext';
import useEventHandlers from './useEventHandlers';
+import { clearAllDrafts } from '~/utils';
import store from '~/store';
-const clearDraft = (conversationId?: string | null) => {
- if (conversationId) {
- localStorage.removeItem(`${LocalStorageKeys.TEXT_DRAFT}${conversationId}`);
- localStorage.removeItem(`${LocalStorageKeys.FILES_DRAFT}${conversationId}`);
- } else {
- localStorage.removeItem(`${LocalStorageKeys.TEXT_DRAFT}${Constants.NEW_CONVO}`);
- localStorage.removeItem(`${LocalStorageKeys.FILES_DRAFT}${Constants.NEW_CONVO}`);
- }
-};
-
type ChatHelpers = Pick<
EventHandlerParams,
| 'setMessages'
@@ -120,7 +104,7 @@ export default function useSSE(
const data = JSON.parse(e.data);
if (data.final != null) {
- clearDraft(submission.conversation?.conversationId);
+ clearAllDrafts(submission.conversation?.conversationId);
try {
finalHandler(data, submission as EventSubmission);
} catch (error) {
diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json
index afd1072b61..9f641fdb16 100644
--- a/client/src/locales/en/translation.json
+++ b/client/src/locales/en/translation.json
@@ -376,6 +376,7 @@
"com_error_no_base_url": "No base URL found. Please provide one and try again.",
"com_error_no_user_key": "No key found. Please provide a key and try again.",
"com_error_refusal": "Response refused by safety filters. Rewrite your message and try again. If you encounter this frequently while using Claude Sonnet 4.5 or Opus 4.1, you can try Sonnet 4, which has different usage restrictions.",
+ "com_error_stream_expired": "The response stream has expired or already completed. Please try again.",
"com_file_pages": "Pages: {{pages}}",
"com_file_source": "File",
"com_file_unknown": "Unknown File",
diff --git a/client/src/utils/drafts.ts b/client/src/utils/drafts.ts
index 1b3172def0..2e47c383b1 100644
--- a/client/src/utils/drafts.ts
+++ b/client/src/utils/drafts.ts
@@ -1,10 +1,17 @@
import debounce from 'lodash/debounce';
-import { LocalStorageKeys } from 'librechat-data-provider';
+import { Constants, LocalStorageKeys } from 'librechat-data-provider';
export const clearDraft = debounce((id?: string | null) => {
localStorage.removeItem(`${LocalStorageKeys.TEXT_DRAFT}${id ?? ''}`);
}, 2500);
+/** Synchronously removes both text and file drafts for a conversation (or NEW_CONVO fallback) */
+export const clearAllDrafts = (conversationId?: string | null) => {
+ const key = conversationId || Constants.NEW_CONVO;
+ localStorage.removeItem(`${LocalStorageKeys.TEXT_DRAFT}${key}`);
+ localStorage.removeItem(`${LocalStorageKeys.FILES_DRAFT}${key}`);
+};
+
export const encodeBase64 = (plainText: string): string => {
try {
const textBytes = new TextEncoder().encode(plainText);
diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts
index e19c69e799..0c8c591488 100644
--- a/packages/data-provider/src/config.ts
+++ b/packages/data-provider/src/config.ts
@@ -1616,6 +1616,10 @@ export enum ErrorTypes {
* Model refused to respond (content policy violation)
*/
REFUSAL = 'refusal',
+ /**
+ * SSE stream 404 — job completed, expired, or was deleted before the subscriber connected
+ */
+ STREAM_EXPIRED = 'stream_expired',
}
/**
From b1899723817d02977f6194794e7db25f9fbbe13c Mon Sep 17 00:00:00 2001
From: Danny Avila
Date: Thu, 19 Mar 2026 14:52:06 -0400
Subject: [PATCH 04/98] =?UTF-8?q?=F0=9F=8E=AD=20fix:=20Set=20Explicit=20Pe?=
=?UTF-8?q?rmission=20Defaults=20for=20USER=20Role=20in=20roleDefaults=20(?=
=?UTF-8?q?#12308)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* fix: set explicit permission defaults for USER role in roleDefaults
Previously several permission types for the USER role had empty
objects in roleDefaults, causing the getPermissionValue fallback to
resolve SHARE/CREATE via the zod schema defaults on fresh installs.
This silently granted users MCP server creation ability and left
share permissions ambiguous.
Sets explicit defaults for all multi-field permission types:
- PROMPTS/AGENTS: USE and CREATE true, SHARE false
- MCP_SERVERS: USE true, CREATE/SHARE false
- REMOTE_AGENTS: all false
Adds regression tests covering the exact reported scenarios (fresh
install with `agents: { use: true }`, restart preserving admin-panel
overrides) and structural guards against future permission schema
expansions missing explicit USER defaults.
Closes #12306.
* fix: guard MCP_SERVERS.CREATE against configDefaults fallback + add migration
The roleDefaults fix alone was insufficient: loadDefaultInterface propagates
configDefaults.mcpServers.create=true as tier-1 in getPermissionValue, overriding
the roleDefault of false. This commit:
- Adds conditional guards for MCP_SERVERS.CREATE and REMOTE_AGENTS.CREATE matching
the existing AGENTS/PROMPTS pattern (only include CREATE when explicitly configured
in yaml OR on fresh install)
- Uses raw interfaceConfig for MCP_SERVERS.CREATE tier-1 instead of loadedInterface
(which includes configDefaults fallback)
- Adds one-time migration backfill: corrects existing MCP_SERVERS.CREATE=true for
USER role in DB when no explicit yaml config is present
- Adds restart-scenario and migration regression tests for MCP_SERVERS
- Cleans up roles.spec.ts: for..of loops, Permissions[] typing, Set for lookups,
removes unnecessary aliases, improves JSDoc for exclusion list
- Fixes misleading test name for agents regression test
- Removes redundant not.toHaveProperty assertions after strict toEqual
* fix: use raw interfaceConfig for REMOTE_AGENTS.CREATE tier-1 (consistency)
Aligns REMOTE_AGENTS.CREATE with the MCP_SERVERS.CREATE fix — reads from
raw interfaceConfig instead of loadedInterface to prevent a future
configDefaults fallback from silently overriding the roleDefault.
---
packages/api/src/app/permissions.spec.ts | 308 ++++++++++++++++++++++-
packages/api/src/app/permissions.ts | 61 ++++-
packages/data-provider/src/roles.spec.ts | 132 ++++++++++
packages/data-provider/src/roles.ts | 28 ++-
4 files changed, 510 insertions(+), 19 deletions(-)
create mode 100644 packages/data-provider/src/roles.spec.ts
diff --git a/packages/api/src/app/permissions.spec.ts b/packages/api/src/app/permissions.spec.ts
index 7ab7e0d0d1..106ebbb50b 100644
--- a/packages/api/src/app/permissions.spec.ts
+++ b/packages/api/src/app/permissions.spec.ts
@@ -398,7 +398,7 @@ describe('updateInterfacePermissions - permissions', () => {
[PermissionTypes.FILE_CITATIONS]: { [Permissions.USE]: true },
[PermissionTypes.MCP_SERVERS]: {
[Permissions.USE]: true,
- [Permissions.CREATE]: true,
+ [Permissions.CREATE]: false,
[Permissions.SHARE]: false,
[Permissions.SHARE_PUBLIC]: false,
},
@@ -555,7 +555,7 @@ describe('updateInterfacePermissions - permissions', () => {
[PermissionTypes.FILE_CITATIONS]: { [Permissions.USE]: true },
[PermissionTypes.MCP_SERVERS]: {
[Permissions.USE]: true,
- [Permissions.CREATE]: true,
+ [Permissions.CREATE]: false,
[Permissions.SHARE]: false,
[Permissions.SHARE_PUBLIC]: false,
},
@@ -699,7 +699,7 @@ describe('updateInterfacePermissions - permissions', () => {
[PermissionTypes.FILE_CITATIONS]: { [Permissions.USE]: true },
[PermissionTypes.MCP_SERVERS]: {
[Permissions.USE]: true,
- [Permissions.CREATE]: true,
+ [Permissions.CREATE]: false,
[Permissions.SHARE]: false,
[Permissions.SHARE_PUBLIC]: false,
},
@@ -848,7 +848,7 @@ describe('updateInterfacePermissions - permissions', () => {
[PermissionTypes.FILE_CITATIONS]: { [Permissions.USE]: true },
[PermissionTypes.MCP_SERVERS]: {
[Permissions.USE]: true,
- [Permissions.CREATE]: true,
+ [Permissions.CREATE]: false,
[Permissions.SHARE]: false,
[Permissions.SHARE_PUBLIC]: false,
},
@@ -1002,7 +1002,7 @@ describe('updateInterfacePermissions - permissions', () => {
[PermissionTypes.FILE_CITATIONS]: { [Permissions.USE]: true },
[PermissionTypes.MCP_SERVERS]: {
[Permissions.USE]: true,
- [Permissions.CREATE]: true,
+ [Permissions.CREATE]: false,
[Permissions.SHARE]: false,
[Permissions.SHARE_PUBLIC]: false,
},
@@ -2194,4 +2194,302 @@ describe('updateInterfacePermissions - permissions', () => {
[Permissions.SHARE_PUBLIC]: false,
});
});
+
+ it('should populate all default agent permissions on fresh install with object use config (regression: #12306)', async () => {
+ const config = {
+ interface: {
+ agents: { use: true },
+ },
+ };
+ const configDefaults = { interface: {} } as TConfigDefaults;
+ const interfaceConfig = await loadDefaultInterface({ config, configDefaults });
+ const appConfig = { config, interfaceConfig } as unknown as AppConfig;
+
+ await updateInterfacePermissions({
+ appConfig,
+ getRoleByName: mockGetRoleByName,
+ updateAccessPermissions: mockUpdateAccessPermissions,
+ });
+
+ const userCall = mockUpdateAccessPermissions.mock.calls.find(
+ (call) => call[0] === SystemRoles.USER,
+ );
+ const adminCall = mockUpdateAccessPermissions.mock.calls.find(
+ (call) => call[0] === SystemRoles.ADMIN,
+ );
+
+ expect(userCall[1][PermissionTypes.AGENTS]).toEqual({
+ [Permissions.USE]: true,
+ [Permissions.CREATE]: true,
+ [Permissions.SHARE]: false,
+ [Permissions.SHARE_PUBLIC]: false,
+ });
+
+ expect(adminCall[1][PermissionTypes.AGENTS]).toEqual({
+ [Permissions.USE]: true,
+ [Permissions.CREATE]: true,
+ [Permissions.SHARE]: true,
+ [Permissions.SHARE_PUBLIC]: true,
+ });
+ });
+
+ it('should preserve admin-panel changes to USER agents.CREATE across restart (regression: #12306 restart)', async () => {
+ mockGetRoleByName.mockResolvedValue({
+ permissions: {
+ [PermissionTypes.AGENTS]: {
+ [Permissions.USE]: true,
+ [Permissions.CREATE]: false,
+ [Permissions.SHARE]: false,
+ [Permissions.SHARE_PUBLIC]: false,
+ },
+ },
+ });
+
+ const config = {
+ interface: {
+ agents: { use: true },
+ },
+ };
+ const configDefaults = { interface: {} } as TConfigDefaults;
+ const interfaceConfig = await loadDefaultInterface({ config, configDefaults });
+ const appConfig = { config, interfaceConfig } as unknown as AppConfig;
+
+ await updateInterfacePermissions({
+ appConfig,
+ getRoleByName: mockGetRoleByName,
+ updateAccessPermissions: mockUpdateAccessPermissions,
+ });
+
+ const userCall = mockUpdateAccessPermissions.mock.calls.find(
+ (call) => call[0] === SystemRoles.USER,
+ );
+
+ expect(userCall[1][PermissionTypes.AGENTS]).toEqual({
+ [Permissions.USE]: true,
+ });
+ });
+
+ it('should preserve all admin-panel changes when agents is not in yaml config (regression: #12306 restart)', async () => {
+ mockGetRoleByName.mockResolvedValue({
+ permissions: {
+ [PermissionTypes.AGENTS]: {
+ [Permissions.USE]: false,
+ [Permissions.CREATE]: false,
+ [Permissions.SHARE]: false,
+ [Permissions.SHARE_PUBLIC]: false,
+ },
+ [PermissionTypes.PROMPTS]: {
+ [Permissions.USE]: false,
+ [Permissions.CREATE]: false,
+ [Permissions.SHARE]: false,
+ [Permissions.SHARE_PUBLIC]: false,
+ },
+ },
+ });
+
+ const config = {};
+ const configDefaults = { interface: {} } as TConfigDefaults;
+ const interfaceConfig = await loadDefaultInterface({ config, configDefaults });
+ const appConfig = { config, interfaceConfig } as unknown as AppConfig;
+
+ await updateInterfacePermissions({
+ appConfig,
+ getRoleByName: mockGetRoleByName,
+ updateAccessPermissions: mockUpdateAccessPermissions,
+ });
+
+ const userCall = mockUpdateAccessPermissions.mock.calls.find(
+ (call) => call[0] === SystemRoles.USER,
+ );
+
+ expect(userCall[1]).not.toHaveProperty(PermissionTypes.AGENTS);
+ expect(userCall[1]).not.toHaveProperty(PermissionTypes.PROMPTS);
+ });
+
+ it('should not grant USER share for prompts when only use is configured (regression: #12306)', async () => {
+ const config = {
+ interface: {
+ prompts: { use: true },
+ },
+ };
+ const configDefaults = { interface: {} } as TConfigDefaults;
+ const interfaceConfig = await loadDefaultInterface({ config, configDefaults });
+ const appConfig = { config, interfaceConfig } as unknown as AppConfig;
+
+ await updateInterfacePermissions({
+ appConfig,
+ getRoleByName: mockGetRoleByName,
+ updateAccessPermissions: mockUpdateAccessPermissions,
+ });
+
+ const userCall = mockUpdateAccessPermissions.mock.calls.find(
+ (call) => call[0] === SystemRoles.USER,
+ );
+ const adminCall = mockUpdateAccessPermissions.mock.calls.find(
+ (call) => call[0] === SystemRoles.ADMIN,
+ );
+
+ expect(userCall[1][PermissionTypes.PROMPTS]).toEqual({
+ [Permissions.USE]: true,
+ [Permissions.CREATE]: true,
+ [Permissions.SHARE]: false,
+ [Permissions.SHARE_PUBLIC]: false,
+ });
+
+ expect(adminCall[1][PermissionTypes.PROMPTS]).toEqual({
+ [Permissions.USE]: true,
+ [Permissions.CREATE]: true,
+ [Permissions.SHARE]: true,
+ [Permissions.SHARE_PUBLIC]: true,
+ });
+ });
+
+ it('should not grant USER create for mcpServers when only use is configured (regression: #12306)', async () => {
+ const config = {
+ interface: {
+ mcpServers: { use: true },
+ },
+ };
+ const configDefaults = { interface: {} } as TConfigDefaults;
+ const interfaceConfig = await loadDefaultInterface({ config, configDefaults });
+ const appConfig = { config, interfaceConfig } as unknown as AppConfig;
+
+ await updateInterfacePermissions({
+ appConfig,
+ getRoleByName: mockGetRoleByName,
+ updateAccessPermissions: mockUpdateAccessPermissions,
+ });
+
+ const userCall = mockUpdateAccessPermissions.mock.calls.find(
+ (call) => call[0] === SystemRoles.USER,
+ );
+ const adminCall = mockUpdateAccessPermissions.mock.calls.find(
+ (call) => call[0] === SystemRoles.ADMIN,
+ );
+
+ expect(userCall[1][PermissionTypes.MCP_SERVERS]).toEqual({
+ [Permissions.USE]: true,
+ [Permissions.CREATE]: false,
+ [Permissions.SHARE]: false,
+ [Permissions.SHARE_PUBLIC]: false,
+ });
+
+ expect(adminCall[1][PermissionTypes.MCP_SERVERS]).toEqual({
+ [Permissions.USE]: true,
+ [Permissions.CREATE]: true,
+ [Permissions.SHARE]: true,
+ [Permissions.SHARE_PUBLIC]: true,
+ });
+ });
+
+ it('should preserve existing MCP_SERVERS permissions on restart when mcpServers not in yaml config (regression: #12306 restart)', async () => {
+ mockGetRoleByName.mockResolvedValue({
+ permissions: {
+ [PermissionTypes.MCP_SERVERS]: {
+ [Permissions.USE]: true,
+ [Permissions.CREATE]: true,
+ [Permissions.SHARE]: false,
+ [Permissions.SHARE_PUBLIC]: false,
+ },
+ },
+ });
+
+ const config = {};
+ const configDefaults = { interface: {} } as TConfigDefaults;
+ const interfaceConfig = await loadDefaultInterface({ config, configDefaults });
+ const appConfig = { config, interfaceConfig } as unknown as AppConfig;
+
+ await updateInterfacePermissions({
+ appConfig,
+ getRoleByName: mockGetRoleByName,
+ updateAccessPermissions: mockUpdateAccessPermissions,
+ });
+
+ const userCall = mockUpdateAccessPermissions.mock.calls.find(
+ (call) => call[0] === SystemRoles.USER,
+ );
+
+ expect(userCall[1][PermissionTypes.MCP_SERVERS]).toEqual({
+ [Permissions.CREATE]: false,
+ });
+ });
+
+ it('should migrate existing MCP_SERVERS.CREATE=true to false for USER when no explicit config (regression: #12306 migration)', async () => {
+ mockGetRoleByName.mockResolvedValue({
+ permissions: {
+ [PermissionTypes.MCP_SERVERS]: {
+ [Permissions.USE]: true,
+ [Permissions.CREATE]: true,
+ [Permissions.SHARE]: false,
+ [Permissions.SHARE_PUBLIC]: false,
+ },
+ [PermissionTypes.AGENTS]: {
+ [Permissions.USE]: true,
+ [Permissions.CREATE]: true,
+ [Permissions.SHARE]: false,
+ [Permissions.SHARE_PUBLIC]: false,
+ },
+ },
+ });
+
+ const config = {};
+ const configDefaults = { interface: {} } as TConfigDefaults;
+ const interfaceConfig = await loadDefaultInterface({ config, configDefaults });
+ const appConfig = { config, interfaceConfig } as unknown as AppConfig;
+
+ await updateInterfacePermissions({
+ appConfig,
+ getRoleByName: mockGetRoleByName,
+ updateAccessPermissions: mockUpdateAccessPermissions,
+ });
+
+ const userCall = mockUpdateAccessPermissions.mock.calls.find(
+ (call) => call[0] === SystemRoles.USER,
+ );
+ const adminCall = mockUpdateAccessPermissions.mock.calls.find(
+ (call) => call[0] === SystemRoles.ADMIN,
+ );
+
+ expect(userCall[1][PermissionTypes.MCP_SERVERS]).toEqual({
+ [Permissions.CREATE]: false,
+ });
+ expect(userCall[1]).not.toHaveProperty(PermissionTypes.AGENTS);
+
+ expect(adminCall[1]).not.toHaveProperty(PermissionTypes.MCP_SERVERS);
+ expect(adminCall[1]).not.toHaveProperty(PermissionTypes.AGENTS);
+ });
+
+ it('should NOT migrate MCP_SERVERS.CREATE when yaml explicitly sets create: true (regression: #12306 migration)', async () => {
+ mockGetRoleByName.mockResolvedValue({
+ permissions: {
+ [PermissionTypes.MCP_SERVERS]: {
+ [Permissions.USE]: true,
+ [Permissions.CREATE]: true,
+ [Permissions.SHARE]: false,
+ [Permissions.SHARE_PUBLIC]: false,
+ },
+ },
+ });
+
+ const config = {
+ interface: {
+ mcpServers: { use: true, create: true },
+ },
+ };
+ const configDefaults = { interface: {} } as TConfigDefaults;
+ const interfaceConfig = await loadDefaultInterface({ config, configDefaults });
+ const appConfig = { config, interfaceConfig } as unknown as AppConfig;
+
+ await updateInterfacePermissions({
+ appConfig,
+ getRoleByName: mockGetRoleByName,
+ updateAccessPermissions: mockUpdateAccessPermissions,
+ });
+
+ const userCall = mockUpdateAccessPermissions.mock.calls.find(
+ (call) => call[0] === SystemRoles.USER,
+ );
+
+ expect(userCall[1][PermissionTypes.MCP_SERVERS][Permissions.CREATE]).toBe(true);
+ });
});
diff --git a/packages/api/src/app/permissions.ts b/packages/api/src/app/permissions.ts
index 3638bdc0bb..5a557adfcf 100644
--- a/packages/api/src/app/permissions.ts
+++ b/packages/api/src/app/permissions.ts
@@ -352,11 +352,19 @@ export async function updateInterfacePermissions({
defaultPerms[PermissionTypes.MCP_SERVERS]?.[Permissions.USE],
defaults.mcpServers?.use,
),
- [Permissions.CREATE]: getPermissionValue(
- loadedInterface.mcpServers?.create,
- defaultPerms[PermissionTypes.MCP_SERVERS]?.[Permissions.CREATE],
- defaults.mcpServers?.create,
- ),
+ ...((typeof interfaceConfig?.mcpServers === 'object' &&
+ 'create' in interfaceConfig.mcpServers) ||
+ !existingPermissions?.[PermissionTypes.MCP_SERVERS]
+ ? {
+ [Permissions.CREATE]: getPermissionValue(
+ typeof interfaceConfig?.mcpServers === 'object'
+ ? interfaceConfig.mcpServers.create
+ : undefined,
+ defaultPerms[PermissionTypes.MCP_SERVERS]?.[Permissions.CREATE],
+ defaults.mcpServers?.create,
+ ),
+ }
+ : {}),
...((typeof interfaceConfig?.mcpServers === 'object' &&
('share' in interfaceConfig.mcpServers || 'public' in interfaceConfig.mcpServers)) ||
!existingPermissions?.[PermissionTypes.MCP_SERVERS]
@@ -380,11 +388,19 @@ export async function updateInterfacePermissions({
defaultPerms[PermissionTypes.REMOTE_AGENTS]?.[Permissions.USE],
defaults.remoteAgents?.use,
),
- [Permissions.CREATE]: getPermissionValue(
- loadedInterface.remoteAgents?.create,
- defaultPerms[PermissionTypes.REMOTE_AGENTS]?.[Permissions.CREATE],
- defaults.remoteAgents?.create,
- ),
+ ...((typeof interfaceConfig?.remoteAgents === 'object' &&
+ 'create' in interfaceConfig.remoteAgents) ||
+ !existingPermissions?.[PermissionTypes.REMOTE_AGENTS]
+ ? {
+ [Permissions.CREATE]: getPermissionValue(
+ typeof interfaceConfig?.remoteAgents === 'object'
+ ? interfaceConfig.remoteAgents.create
+ : undefined,
+ defaultPerms[PermissionTypes.REMOTE_AGENTS]?.[Permissions.CREATE],
+ defaults.remoteAgents?.create,
+ ),
+ }
+ : {}),
...((typeof interfaceConfig?.remoteAgents === 'object' &&
('share' in interfaceConfig.remoteAgents || 'public' in interfaceConfig.remoteAgents)) ||
!existingPermissions?.[PermissionTypes.REMOTE_AGENTS]
@@ -511,6 +527,31 @@ export async function updateInterfacePermissions({
}
}
+ /**
+ * One-time migration: correct MCP_SERVERS.CREATE for USER role.
+ * Before the explicit roleDefaults fix, Zod schema defaults resolved CREATE to true
+ * for all roles. ADMIN should keep CREATE: true, but USER should have CREATE: false
+ * unless explicitly configured otherwise in librechat.yaml.
+ */
+ if (roleName === SystemRoles.USER) {
+ const existingMcpPerms = existingPermissions?.[PermissionTypes.MCP_SERVERS];
+ const mcpCreateExplicit =
+ typeof interfaceConfig?.mcpServers === 'object' && 'create' in interfaceConfig.mcpServers;
+ if (
+ existingMcpPerms?.[Permissions.CREATE] === true &&
+ !mcpCreateExplicit &&
+ defaultPerms[PermissionTypes.MCP_SERVERS]?.[Permissions.CREATE] === false
+ ) {
+ logger.debug(
+ `Role '${roleName}': Migrating MCP_SERVERS.CREATE from true to false (Zod default correction)`,
+ );
+ permissionsToUpdate[PermissionTypes.MCP_SERVERS] = {
+ ...permissionsToUpdate[PermissionTypes.MCP_SERVERS],
+ [Permissions.CREATE]: false,
+ };
+ }
+ }
+
// Update permissions if any need updating
if (Object.keys(permissionsToUpdate).length > 0) {
await updateAccessPermissions(roleName, permissionsToUpdate, existingRole);
diff --git a/packages/data-provider/src/roles.spec.ts b/packages/data-provider/src/roles.spec.ts
new file mode 100644
index 0000000000..60dac5ab50
--- /dev/null
+++ b/packages/data-provider/src/roles.spec.ts
@@ -0,0 +1,132 @@
+import { Permissions, PermissionTypes, permissionsSchema } from './permissions';
+import { SystemRoles, roleDefaults } from './roles';
+
+const RESOURCE_MANAGEMENT_FIELDS: Permissions[] = [
+ Permissions.CREATE,
+ Permissions.SHARE,
+ Permissions.SHARE_PUBLIC,
+];
+
+/**
+ * Permission types where CREATE/SHARE/SHARE_PUBLIC must default to false for USER.
+ * MEMORIES is excluded: its CREATE/READ/UPDATE apply to the user's own private data.
+ * AGENTS/PROMPTS are excluded: CREATE=true is intentional (users own their agents/prompts).
+ * Add new types here if they gate shared/multi-user resources.
+ */
+const RESOURCE_PERMISSION_TYPES: PermissionTypes[] = [
+ PermissionTypes.MCP_SERVERS,
+ PermissionTypes.REMOTE_AGENTS,
+];
+
+describe('roleDefaults', () => {
+ describe('USER role', () => {
+ const userPerms = roleDefaults[SystemRoles.USER].permissions;
+
+ it('should have explicit values for every field in every multi-field permission type', () => {
+ const schemaShape = permissionsSchema.shape;
+
+ for (const [permType, subSchema] of Object.entries(schemaShape)) {
+ const fieldNames = Object.keys(subSchema.shape);
+ if (fieldNames.length <= 1) {
+ continue;
+ }
+
+ const userValues =
+ userPerms[permType as PermissionTypes] as Record;
+
+ for (const field of fieldNames) {
+ expect({
+ permType,
+ field,
+ value: userValues[field],
+ }).toEqual(
+ expect.objectContaining({
+ permType,
+ field,
+ value: expect.any(Boolean),
+ }),
+ );
+ }
+ }
+ });
+
+ it('should never grant CREATE, SHARE, or SHARE_PUBLIC by default for resource-management types', () => {
+ for (const permType of RESOURCE_PERMISSION_TYPES) {
+ const permissions = userPerms[permType] as Record;
+ for (const field of RESOURCE_MANAGEMENT_FIELDS) {
+ if (permissions[field] === undefined) {
+ continue;
+ }
+ expect({
+ permType,
+ field,
+ value: permissions[field],
+ }).toEqual(
+ expect.objectContaining({
+ permType,
+ field,
+ value: false,
+ }),
+ );
+ }
+ }
+ });
+
+ it('should cover every permission type that has CREATE, SHARE, or SHARE_PUBLIC fields', () => {
+ const schemaShape = permissionsSchema.shape;
+ const restrictedSet = new Set(RESOURCE_PERMISSION_TYPES);
+
+ for (const [permType, subSchema] of Object.entries(schemaShape)) {
+ const fieldNames = Object.keys(subSchema.shape);
+ const hasResourceFields = fieldNames.some((f) => RESOURCE_MANAGEMENT_FIELDS.includes(f as Permissions));
+ if (!hasResourceFields) {
+ continue;
+ }
+
+ const isTracked =
+ restrictedSet.has(permType) ||
+ permType === PermissionTypes.MEMORIES ||
+ permType === PermissionTypes.PROMPTS ||
+ permType === PermissionTypes.AGENTS;
+
+ expect({
+ permType,
+ tracked: isTracked,
+ }).toEqual(
+ expect.objectContaining({
+ permType,
+ tracked: true,
+ }),
+ );
+ }
+ });
+ });
+
+ describe('ADMIN role', () => {
+ const adminPerms = roleDefaults[SystemRoles.ADMIN].permissions;
+
+ it('should have explicit values for every field in every permission type', () => {
+ const schemaShape = permissionsSchema.shape;
+
+ for (const [permType, subSchema] of Object.entries(schemaShape)) {
+ const fieldNames = Object.keys(subSchema.shape);
+ const adminValues =
+ adminPerms[permType as PermissionTypes] as Record;
+
+ for (const field of fieldNames) {
+ expect({
+ permType,
+ field,
+ value: adminValues[field],
+ }).toEqual(
+ expect.objectContaining({
+ permType,
+ field,
+ value: expect.any(Boolean),
+ }),
+ );
+ }
+ }
+ });
+ });
+});
diff --git a/packages/data-provider/src/roles.ts b/packages/data-provider/src/roles.ts
index b494ee5817..1ba7a8cce2 100644
--- a/packages/data-provider/src/roles.ts
+++ b/packages/data-provider/src/roles.ts
@@ -180,10 +180,20 @@ export const roleDefaults = defaultRolesSchema.parse({
[SystemRoles.USER]: {
name: SystemRoles.USER,
permissions: {
- [PermissionTypes.PROMPTS]: {},
+ [PermissionTypes.PROMPTS]: {
+ [Permissions.USE]: true,
+ [Permissions.CREATE]: true,
+ [Permissions.SHARE]: false,
+ [Permissions.SHARE_PUBLIC]: false,
+ },
[PermissionTypes.BOOKMARKS]: {},
[PermissionTypes.MEMORIES]: {},
- [PermissionTypes.AGENTS]: {},
+ [PermissionTypes.AGENTS]: {
+ [Permissions.USE]: true,
+ [Permissions.CREATE]: true,
+ [Permissions.SHARE]: false,
+ [Permissions.SHARE_PUBLIC]: false,
+ },
[PermissionTypes.MULTI_CONVO]: {},
[PermissionTypes.TEMPORARY_CHAT]: {},
[PermissionTypes.RUN_CODE]: {},
@@ -198,8 +208,18 @@ export const roleDefaults = defaultRolesSchema.parse({
},
[PermissionTypes.FILE_SEARCH]: {},
[PermissionTypes.FILE_CITATIONS]: {},
- [PermissionTypes.MCP_SERVERS]: {},
- [PermissionTypes.REMOTE_AGENTS]: {},
+ [PermissionTypes.MCP_SERVERS]: {
+ [Permissions.USE]: true,
+ [Permissions.CREATE]: false,
+ [Permissions.SHARE]: false,
+ [Permissions.SHARE_PUBLIC]: false,
+ },
+ [PermissionTypes.REMOTE_AGENTS]: {
+ [Permissions.USE]: false,
+ [Permissions.CREATE]: false,
+ [Permissions.SHARE]: false,
+ [Permissions.SHARE_PUBLIC]: false,
+ },
},
},
});
From 93952f06b42e35fe98069de6bc5e2621379c27e9 Mon Sep 17 00:00:00 2001
From: Danny Avila
Date: Thu, 19 Mar 2026 15:15:10 -0400
Subject: [PATCH 05/98] =?UTF-8?q?=F0=9F=A7=AF=20fix:=20Remove=20Revoked=20?=
=?UTF-8?q?Agents=20from=20User=20Favorites=20(#12296)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* 🧯 fix: Remove revoked agents from user favorites
When agent access is revoked, the agent remained in the user's favorites
causing repeated 403 errors on page load. Backend now cleans up favorites
on permission revocation; frontend treats 403 like 404 and auto-removes
stale agent references.
* 🧪 fix: Address review findings for stale agent favorites cleanup
- Guard cleanup effect with ref to prevent infinite loop on mutation
failure (Finding 1)
- Use validated results.revoked instead of raw request payload for
revokedUserIds (Finding 3)
- Stabilize staleAgentIds memo with string key to avoid spurious
re-evaluation during drag-drop (Finding 5)
- Add JSDoc with param types to removeRevokedAgentFromFavorites
(Finding 7)
- Return promise from removeRevokedAgentFromFavorites for testability
- Add 7 backend tests covering revocation cleanup paths
- Add 3 frontend tests for 403 handling and stale cleanup persistence
---
.../controllers/PermissionsController.js | 34 ++-
.../__tests__/PermissionsController.spec.js | 268 ++++++++++++++++++
.../Nav/Favorites/FavoritesList.tsx | 29 +-
.../Favorites/tests/FavoritesList.spec.tsx | 81 ++++++
4 files changed, 409 insertions(+), 3 deletions(-)
create mode 100644 api/server/controllers/__tests__/PermissionsController.spec.js
diff --git a/api/server/controllers/PermissionsController.js b/api/server/controllers/PermissionsController.js
index 51993d083c..16930c5139 100644
--- a/api/server/controllers/PermissionsController.js
+++ b/api/server/controllers/PermissionsController.js
@@ -24,7 +24,7 @@ const {
entraIdPrincipalFeatureEnabled,
searchEntraIdPrincipals,
} = require('~/server/services/GraphApiService');
-const { AclEntry, AccessRole } = require('~/db/models');
+const { Agent, AclEntry, AccessRole, User } = require('~/db/models');
/**
* Generic controller for resource permission endpoints
@@ -43,6 +43,28 @@ const validateResourceType = (resourceType) => {
}
};
+/**
+ * Removes an agent from the favorites of specified users (fire-and-forget).
+ * Both AGENT and REMOTE_AGENT resource types share the Agent collection.
+ * @param {string} resourceId - The agent's MongoDB ObjectId hex string
+ * @param {string[]} userIds - User ObjectId strings whose favorites should be cleaned
+ */
+const removeRevokedAgentFromFavorites = (resourceId, userIds) =>
+ Agent.findOne({ _id: resourceId }, { id: 1 })
+ .lean()
+ .then((agent) => {
+ if (!agent) {
+ return;
+ }
+ return User.updateMany(
+ { _id: { $in: userIds }, 'favorites.agentId': agent.id },
+ { $pull: { favorites: { agentId: agent.id } } },
+ );
+ })
+ .catch((err) => {
+ logger.error('[removeRevokedAgentFromFavorites] Error cleaning up favorites', err);
+ });
+
/**
* Bulk update permissions for a resource (grant, update, remove)
* @route PUT /api/{resourceType}/{resourceId}/permissions
@@ -155,6 +177,16 @@ const updateResourcePermissions = async (req, res) => {
grantedBy: userId,
});
+ const isAgentResource =
+ resourceType === ResourceType.AGENT || resourceType === ResourceType.REMOTE_AGENT;
+ const revokedUserIds = results.revoked
+ .filter((p) => p.type === PrincipalType.USER && p.id)
+ .map((p) => p.id);
+
+ if (isAgentResource && revokedUserIds.length > 0) {
+ removeRevokedAgentFromFavorites(resourceId, revokedUserIds);
+ }
+
/** @type {TUpdateResourcePermissionsResponse} */
const response = {
message: 'Permissions updated successfully',
diff --git a/api/server/controllers/__tests__/PermissionsController.spec.js b/api/server/controllers/__tests__/PermissionsController.spec.js
new file mode 100644
index 0000000000..840eaf0c30
--- /dev/null
+++ b/api/server/controllers/__tests__/PermissionsController.spec.js
@@ -0,0 +1,268 @@
+const mongoose = require('mongoose');
+
+const mockLogger = { error: jest.fn(), warn: jest.fn(), info: jest.fn(), debug: jest.fn() };
+
+jest.mock('@librechat/data-schemas', () => ({
+ logger: mockLogger,
+}));
+
+const { ResourceType, PrincipalType } = jest.requireActual('librechat-data-provider');
+
+jest.mock('librechat-data-provider', () => ({
+ ...jest.requireActual('librechat-data-provider'),
+}));
+
+jest.mock('@librechat/api', () => ({
+ enrichRemoteAgentPrincipals: jest.fn(),
+ backfillRemoteAgentPermissions: jest.fn(),
+}));
+
+const mockBulkUpdateResourcePermissions = jest.fn();
+
+jest.mock('~/server/services/PermissionService', () => ({
+ bulkUpdateResourcePermissions: (...args) => mockBulkUpdateResourcePermissions(...args),
+ ensureGroupPrincipalExists: jest.fn(),
+ getEffectivePermissions: jest.fn(),
+ ensurePrincipalExists: jest.fn(),
+ getAvailableRoles: jest.fn(),
+ findAccessibleResources: jest.fn(),
+ getResourcePermissionsMap: jest.fn(),
+}));
+
+jest.mock('~/models', () => ({
+ searchPrincipals: jest.fn(),
+ sortPrincipalsByRelevance: jest.fn(),
+ calculateRelevanceScore: jest.fn(),
+}));
+
+jest.mock('~/server/services/GraphApiService', () => ({
+ entraIdPrincipalFeatureEnabled: jest.fn(() => false),
+ searchEntraIdPrincipals: jest.fn(),
+}));
+
+const mockAgentFindOne = jest.fn();
+const mockUserUpdateMany = jest.fn();
+
+jest.mock('~/db/models', () => ({
+ Agent: {
+ findOne: (...args) => mockAgentFindOne(...args),
+ },
+ AclEntry: {},
+ AccessRole: {},
+ User: {
+ updateMany: (...args) => mockUserUpdateMany(...args),
+ },
+}));
+
+const { updateResourcePermissions } = require('../PermissionsController');
+
+const createMockReq = (overrides = {}) => ({
+ params: { resourceType: ResourceType.AGENT, resourceId: '507f1f77bcf86cd799439011' },
+ body: { updated: [], removed: [], public: false },
+ user: { id: 'user-1', role: 'USER' },
+ headers: { authorization: '' },
+ ...overrides,
+});
+
+const createMockRes = () => {
+ const res = {};
+ res.status = jest.fn().mockReturnValue(res);
+ res.json = jest.fn().mockReturnValue(res);
+ return res;
+};
+
+const flushPromises = () => new Promise((resolve) => setImmediate(resolve));
+
+describe('PermissionsController', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('updateResourcePermissions — favorites cleanup', () => {
+ const agentObjectId = new mongoose.Types.ObjectId().toString();
+ const revokedUserId = new mongoose.Types.ObjectId().toString();
+
+ beforeEach(() => {
+ mockBulkUpdateResourcePermissions.mockResolvedValue({
+ granted: [],
+ updated: [],
+ revoked: [{ type: PrincipalType.USER, id: revokedUserId, name: 'Revoked User' }],
+ errors: [],
+ });
+
+ mockAgentFindOne.mockReturnValue({
+ lean: () => Promise.resolve({ _id: agentObjectId, id: 'agent_abc123' }),
+ });
+ mockUserUpdateMany.mockResolvedValue({ modifiedCount: 1 });
+ });
+
+ it('removes agent from revoked users favorites on AGENT resource type', async () => {
+ const req = createMockReq({
+ params: { resourceType: ResourceType.AGENT, resourceId: agentObjectId },
+ body: {
+ updated: [],
+ removed: [{ type: PrincipalType.USER, id: revokedUserId }],
+ public: false,
+ },
+ });
+ const res = createMockRes();
+
+ await updateResourcePermissions(req, res);
+ await flushPromises();
+
+ expect(res.status).toHaveBeenCalledWith(200);
+ expect(mockAgentFindOne).toHaveBeenCalledWith({ _id: agentObjectId }, { id: 1 });
+ expect(mockUserUpdateMany).toHaveBeenCalledWith(
+ { _id: { $in: [revokedUserId] }, 'favorites.agentId': 'agent_abc123' },
+ { $pull: { favorites: { agentId: 'agent_abc123' } } },
+ );
+ });
+
+ it('removes agent from revoked users favorites on REMOTE_AGENT resource type', async () => {
+ const req = createMockReq({
+ params: { resourceType: ResourceType.REMOTE_AGENT, resourceId: agentObjectId },
+ body: {
+ updated: [],
+ removed: [{ type: PrincipalType.USER, id: revokedUserId }],
+ public: false,
+ },
+ });
+ const res = createMockRes();
+
+ await updateResourcePermissions(req, res);
+ await flushPromises();
+
+ expect(mockAgentFindOne).toHaveBeenCalledWith({ _id: agentObjectId }, { id: 1 });
+ expect(mockUserUpdateMany).toHaveBeenCalled();
+ });
+
+ it('uses results.revoked (validated) not raw request payload', async () => {
+ const validId = new mongoose.Types.ObjectId().toString();
+ const invalidId = 'not-a-valid-id';
+
+ mockBulkUpdateResourcePermissions.mockResolvedValue({
+ granted: [],
+ updated: [],
+ revoked: [{ type: PrincipalType.USER, id: validId }],
+ errors: [{ principal: { type: PrincipalType.USER, id: invalidId }, error: 'Invalid ID' }],
+ });
+
+ const req = createMockReq({
+ params: { resourceType: ResourceType.AGENT, resourceId: agentObjectId },
+ body: {
+ updated: [],
+ removed: [
+ { type: PrincipalType.USER, id: validId },
+ { type: PrincipalType.USER, id: invalidId },
+ ],
+ public: false,
+ },
+ });
+ const res = createMockRes();
+
+ await updateResourcePermissions(req, res);
+ await flushPromises();
+
+ expect(mockUserUpdateMany).toHaveBeenCalledWith(
+ expect.objectContaining({ _id: { $in: [validId] } }),
+ expect.any(Object),
+ );
+ });
+
+ it('skips cleanup when no USER principals are revoked', async () => {
+ mockBulkUpdateResourcePermissions.mockResolvedValue({
+ granted: [],
+ updated: [],
+ revoked: [{ type: PrincipalType.GROUP, id: 'group-1' }],
+ errors: [],
+ });
+
+ const req = createMockReq({
+ params: { resourceType: ResourceType.AGENT, resourceId: agentObjectId },
+ body: {
+ updated: [],
+ removed: [{ type: PrincipalType.GROUP, id: 'group-1' }],
+ public: false,
+ },
+ });
+ const res = createMockRes();
+
+ await updateResourcePermissions(req, res);
+ await flushPromises();
+
+ expect(mockAgentFindOne).not.toHaveBeenCalled();
+ expect(mockUserUpdateMany).not.toHaveBeenCalled();
+ });
+
+ it('skips cleanup for non-agent resource types', async () => {
+ mockBulkUpdateResourcePermissions.mockResolvedValue({
+ granted: [],
+ updated: [],
+ revoked: [{ type: PrincipalType.USER, id: revokedUserId }],
+ errors: [],
+ });
+
+ const req = createMockReq({
+ params: { resourceType: ResourceType.PROMPTGROUP, resourceId: agentObjectId },
+ body: {
+ updated: [],
+ removed: [{ type: PrincipalType.USER, id: revokedUserId }],
+ public: false,
+ },
+ });
+ const res = createMockRes();
+
+ await updateResourcePermissions(req, res);
+ await flushPromises();
+
+ expect(res.status).toHaveBeenCalledWith(200);
+ expect(mockAgentFindOne).not.toHaveBeenCalled();
+ });
+
+ it('handles agent not found gracefully', async () => {
+ mockAgentFindOne.mockReturnValue({
+ lean: () => Promise.resolve(null),
+ });
+
+ const req = createMockReq({
+ params: { resourceType: ResourceType.AGENT, resourceId: agentObjectId },
+ body: {
+ updated: [],
+ removed: [{ type: PrincipalType.USER, id: revokedUserId }],
+ public: false,
+ },
+ });
+ const res = createMockRes();
+
+ await updateResourcePermissions(req, res);
+ await flushPromises();
+
+ expect(mockAgentFindOne).toHaveBeenCalled();
+ expect(mockUserUpdateMany).not.toHaveBeenCalled();
+ expect(res.status).toHaveBeenCalledWith(200);
+ });
+
+ it('logs error when User.updateMany fails without blocking response', async () => {
+ mockUserUpdateMany.mockRejectedValue(new Error('DB connection lost'));
+
+ const req = createMockReq({
+ params: { resourceType: ResourceType.AGENT, resourceId: agentObjectId },
+ body: {
+ updated: [],
+ removed: [{ type: PrincipalType.USER, id: revokedUserId }],
+ public: false,
+ },
+ });
+ const res = createMockRes();
+
+ await updateResourcePermissions(req, res);
+ await flushPromises();
+
+ expect(res.status).toHaveBeenCalledWith(200);
+ expect(mockLogger.error).toHaveBeenCalledWith(
+ '[removeRevokedAgentFromFavorites] Error cleaning up favorites',
+ expect.any(Error),
+ );
+ });
+ });
+});
diff --git a/client/src/components/Nav/Favorites/FavoritesList.tsx b/client/src/components/Nav/Favorites/FavoritesList.tsx
index 82225733fd..0ca23f8853 100644
--- a/client/src/components/Nav/Favorites/FavoritesList.tsx
+++ b/client/src/components/Nav/Favorites/FavoritesList.tsx
@@ -198,7 +198,8 @@ export default function FavoritesList({
} catch (error) {
if (error && typeof error === 'object' && 'response' in error) {
const axiosError = error as { response?: { status?: number } };
- if (axiosError.response?.status === 404) {
+ const status = axiosError.response?.status;
+ if (status === 404 || status === 403) {
return { found: false };
}
}
@@ -206,10 +207,34 @@ export default function FavoritesList({
}
},
staleTime: 1000 * 60 * 5,
- enabled: missingAgentIds.length > 0,
})),
});
+ const staleAgentIdsKey = useMemo(() => {
+ const ids: string[] = [];
+ for (let i = 0; i < missingAgentIds.length; i++) {
+ const query = missingAgentQueries[i];
+ if (query.data && !query.data.found) {
+ ids.push(missingAgentIds[i]);
+ }
+ }
+ return ids.sort().join(',');
+ }, [missingAgentIds, missingAgentQueries]);
+
+ const cleanupAttemptedRef = useRef('');
+
+ useEffect(() => {
+ if (!staleAgentIdsKey || cleanupAttemptedRef.current === staleAgentIdsKey) {
+ return;
+ }
+ const staleSet = new Set(staleAgentIdsKey.split(','));
+ const cleaned = safeFavorites.filter((f) => !f.agentId || !staleSet.has(f.agentId));
+ if (cleaned.length < safeFavorites.length) {
+ cleanupAttemptedRef.current = staleAgentIdsKey;
+ reorderFavorites(cleaned, true);
+ }
+ }, [staleAgentIdsKey, safeFavorites, reorderFavorites]);
+
const combinedAgentsMap = useMemo(() => {
if (agentsMap === undefined) {
return undefined;
diff --git a/client/src/components/Nav/Favorites/tests/FavoritesList.spec.tsx b/client/src/components/Nav/Favorites/tests/FavoritesList.spec.tsx
index ed71221de3..74228dc169 100644
--- a/client/src/components/Nav/Favorites/tests/FavoritesList.spec.tsx
+++ b/client/src/components/Nav/Favorites/tests/FavoritesList.spec.tsx
@@ -188,5 +188,86 @@ describe('FavoritesList', () => {
// No favorite items should be rendered (deleted agent is filtered out)
expect(queryAllByTestId('favorite-item')).toHaveLength(0);
});
+
+ it('should treat 403 the same as 404 — agent not rendered', async () => {
+ const validAgent: t.Agent = {
+ id: 'valid-agent',
+ name: 'Valid Agent',
+ author: 'test-author',
+ } as t.Agent;
+
+ mockFavorites.push({ agentId: 'valid-agent' }, { agentId: 'revoked-agent' });
+
+ (dataService.getAgentById as jest.Mock).mockImplementation(
+ ({ agent_id }: { agent_id: string }) => {
+ if (agent_id === 'valid-agent') {
+ return Promise.resolve(validAgent);
+ }
+ if (agent_id === 'revoked-agent') {
+ return Promise.reject({ response: { status: 403 } });
+ }
+ return Promise.reject(new Error('Unknown agent'));
+ },
+ );
+
+ const { findAllByTestId } = renderWithProviders();
+
+ const favoriteItems = await findAllByTestId('favorite-item');
+ expect(favoriteItems).toHaveLength(1);
+ expect(favoriteItems[0]).toHaveTextContent('Valid Agent');
+ });
+
+ it('should call reorderFavorites to persist removal of stale agents', async () => {
+ const mockReorderFavorites = jest.fn().mockResolvedValue(undefined);
+ mockUseFavorites.mockReturnValue({
+ favorites: [{ agentId: 'revoked-agent' }],
+ reorderFavorites: mockReorderFavorites,
+ isLoading: false,
+ });
+
+ (dataService.getAgentById as jest.Mock).mockRejectedValue({ response: { status: 403 } });
+
+ renderWithProviders();
+
+ await waitFor(() => {
+ expect(mockReorderFavorites).toHaveBeenCalledWith([], true);
+ });
+ });
+
+ it('should only attempt cleanup once even when favorites revert to stale state', async () => {
+ const mockReorderFavorites = jest.fn().mockResolvedValue(undefined);
+
+ mockUseFavorites.mockReturnValue({
+ favorites: [{ agentId: 'revoked-agent' }],
+ reorderFavorites: mockReorderFavorites,
+ isLoading: false,
+ });
+
+ (dataService.getAgentById as jest.Mock).mockRejectedValue({ response: { status: 403 } });
+
+ const { rerender } = renderWithProviders();
+
+ await waitFor(() => {
+ expect(mockReorderFavorites).toHaveBeenCalledWith([], true);
+ });
+
+ expect(mockReorderFavorites).toHaveBeenCalledTimes(1);
+
+ rerender(
+
+
+
+
+
+
+
+
+ ,
+ );
+
+ await new Promise((r) => setTimeout(r, 50));
+
+ expect(mockReorderFavorites).toHaveBeenCalledTimes(1);
+ });
});
});
From a88bfae4dd6cb88811f2b77deb82edf5233a412f Mon Sep 17 00:00:00 2001
From: Danny Avila
Date: Thu, 19 Mar 2026 15:33:46 -0400
Subject: [PATCH 06/98] =?UTF-8?q?=F0=9F=96=BC=EF=B8=8F=20fix:=20Correct=20?=
=?UTF-8?q?ToolMessage=20Response=20Format=20for=20Agent-Mode=20Image=20To?=
=?UTF-8?q?ols=20(#12310)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* fix: Set response format for agent tools in DALLE3, FluxAPI, and StableDiffusion classes
- Added logic to set `responseFormat` to 'content_and_artifact' when `isAgent` is true in DALLE3.js, FluxAPI.js, and StableDiffusion.js.
* test: Add regression tests for image tool agent mode in imageTools-agent.spec.js
- Introduced a new test suite for DALLE3, FluxAPI, and StableDiffusion classes to verify that the invoke() method returns a ToolMessage with base64 in artifact.content, ensuring it is not serialized into content.
- Validated that responseFormat is set to 'content_and_artifact' when isAgent is true, and confirmed the correct handling of base64 data in the response.
* fix: handle agent error paths and generateFinetunedImage in image tools
- StableDiffusion._call() was returning a raw string on API error, bypassing
returnValue() and breaking the content_and_artifact contract when isAgent is true
- FluxAPI.generateFinetunedImage() had no isAgent branch; it would call
processFileURL (unset in agent context) instead of fetching and returning
the base64 image as an artifact tuple
- Add JSDoc to all three responseFormat assignments clarifying why LangChain
requires this property for correct ToolMessage construction
* test: expand image tool agent mode regression suite
- Add env var save/restore in beforeEach/afterEach to prevent test pollution
- Add error path tests for all three tools verifying ToolMessage content and
artifact are correctly populated when the upstream API fails
- Add generate_finetuned action test for FluxAPI covering the new agent branch
in generateFinetunedImage
* chore: fix lint errors in FluxAPI and imageTools-agent spec
* chore: fix import ordering in imageTools-agent spec
---
api/app/clients/tools/structured/DALLE3.js | 4 +
api/app/clients/tools/structured/FluxAPI.js | 42 ++-
.../tools/structured/StableDiffusion.js | 6 +-
.../structured/specs/imageTools-agent.spec.js | 294 ++++++++++++++++++
4 files changed, 338 insertions(+), 8 deletions(-)
create mode 100644 api/app/clients/tools/structured/specs/imageTools-agent.spec.js
diff --git a/api/app/clients/tools/structured/DALLE3.js b/api/app/clients/tools/structured/DALLE3.js
index 26610f73ba..c48db1d764 100644
--- a/api/app/clients/tools/structured/DALLE3.js
+++ b/api/app/clients/tools/structured/DALLE3.js
@@ -51,6 +51,10 @@ class DALLE3 extends Tool {
this.fileStrategy = fields.fileStrategy;
/** @type {boolean} */
this.isAgent = fields.isAgent;
+ if (this.isAgent) {
+ /** Ensures LangChain maps [content, artifact] tuple to ToolMessage fields instead of serializing it into content. */
+ this.responseFormat = 'content_and_artifact';
+ }
if (fields.processFileURL) {
/** @type {processFileURL} Necessary for output to contain all image metadata. */
this.processFileURL = fields.processFileURL.bind(this);
diff --git a/api/app/clients/tools/structured/FluxAPI.js b/api/app/clients/tools/structured/FluxAPI.js
index 56f86a707d..f8341f7904 100644
--- a/api/app/clients/tools/structured/FluxAPI.js
+++ b/api/app/clients/tools/structured/FluxAPI.js
@@ -113,6 +113,10 @@ class FluxAPI extends Tool {
/** @type {boolean} **/
this.isAgent = fields.isAgent;
+ if (this.isAgent) {
+ /** Ensures LangChain maps [content, artifact] tuple to ToolMessage fields instead of serializing it into content. */
+ this.responseFormat = 'content_and_artifact';
+ }
this.returnMetadata = fields.returnMetadata ?? false;
if (fields.processFileURL) {
@@ -524,10 +528,40 @@ class FluxAPI extends Tool {
return this.returnValue('No image data received from Flux API.');
}
- // Try saving the image locally
const imageUrl = resultData.sample;
const imageName = `img-${uuidv4()}.png`;
+ if (this.isAgent) {
+ try {
+ const fetchOptions = {};
+ if (process.env.PROXY) {
+ fetchOptions.agent = new HttpsProxyAgent(process.env.PROXY);
+ }
+ const imageResponse = await fetch(imageUrl, fetchOptions);
+ const arrayBuffer = await imageResponse.arrayBuffer();
+ const base64 = Buffer.from(arrayBuffer).toString('base64');
+ const content = [
+ {
+ type: ContentTypes.IMAGE_URL,
+ image_url: {
+ url: `data:image/png;base64,${base64}`,
+ },
+ },
+ ];
+
+ const response = [
+ {
+ type: ContentTypes.TEXT,
+ text: displayMessage,
+ },
+ ];
+ return [response, { content }];
+ } catch (error) {
+ logger.error('[FluxAPI] Error processing finetuned image for agent:', error);
+ return this.returnValue(`Failed to process the finetuned image. ${error.message}`);
+ }
+ }
+
try {
logger.debug('[FluxAPI] Saving finetuned image:', imageUrl);
const result = await this.processFileURL({
@@ -541,12 +575,6 @@ class FluxAPI extends Tool {
logger.debug('[FluxAPI] Finetuned image saved to path:', result.filepath);
- // Calculate cost based on endpoint
- const endpointKey = endpoint.includes('ultra')
- ? 'FLUX_PRO_1_1_ULTRA_FINETUNED'
- : 'FLUX_PRO_FINETUNED';
- const cost = FluxAPI.PRICING[endpointKey] || 0;
- // Return the result based on returnMetadata flag
this.result = this.returnMetadata ? result : this.wrapInMarkdown(result.filepath);
return this.returnValue(this.result);
} catch (error) {
diff --git a/api/app/clients/tools/structured/StableDiffusion.js b/api/app/clients/tools/structured/StableDiffusion.js
index d7a7a4d96b..8cf4b141bb 100644
--- a/api/app/clients/tools/structured/StableDiffusion.js
+++ b/api/app/clients/tools/structured/StableDiffusion.js
@@ -43,6 +43,10 @@ class StableDiffusionAPI extends Tool {
this.returnMetadata = fields.returnMetadata ?? false;
/** @type {boolean} */
this.isAgent = fields.isAgent;
+ if (this.isAgent) {
+ /** Ensures LangChain maps [content, artifact] tuple to ToolMessage fields instead of serializing it into content. */
+ this.responseFormat = 'content_and_artifact';
+ }
if (fields.uploadImageBuffer) {
/** @type {uploadImageBuffer} Necessary for output to contain all image metadata. */
this.uploadImageBuffer = fields.uploadImageBuffer.bind(this);
@@ -115,7 +119,7 @@ class StableDiffusionAPI extends Tool {
generationResponse = await axios.post(`${url}/sdapi/v1/txt2img`, payload);
} catch (error) {
logger.error('[StableDiffusion] Error while generating image:', error);
- return 'Error making API request.';
+ return this.returnValue('Error making API request.');
}
const image = generationResponse.data.images[0];
diff --git a/api/app/clients/tools/structured/specs/imageTools-agent.spec.js b/api/app/clients/tools/structured/specs/imageTools-agent.spec.js
new file mode 100644
index 0000000000..b82dd87b3f
--- /dev/null
+++ b/api/app/clients/tools/structured/specs/imageTools-agent.spec.js
@@ -0,0 +1,294 @@
+/**
+ * Regression tests for image tool agent mode — verifies that invoke() returns
+ * a ToolMessage with base64 in artifact.content rather than serialized into content.
+ *
+ * Root cause: DALLE3/FluxAPI/StableDiffusion extend LangChain's Tool but did not
+ * set responseFormat = 'content_and_artifact'. LangChain's invoke() would then
+ * JSON.stringify the entire [content, artifact] tuple into ToolMessage.content,
+ * dumping base64 into token counting and causing context exhaustion.
+ */
+
+const axios = require('axios');
+const OpenAI = require('openai');
+const undici = require('undici');
+const fetch = require('node-fetch');
+const { ToolMessage } = require('@langchain/core/messages');
+const { ContentTypes } = require('librechat-data-provider');
+const StableDiffusionAPI = require('../StableDiffusion');
+const FluxAPI = require('../FluxAPI');
+const DALLE3 = require('../DALLE3');
+
+jest.mock('axios');
+jest.mock('openai');
+jest.mock('node-fetch');
+jest.mock('undici', () => ({
+ ProxyAgent: jest.fn(),
+ fetch: jest.fn(),
+}));
+jest.mock('@librechat/data-schemas', () => ({
+ logger: { info: jest.fn(), warn: jest.fn(), debug: jest.fn(), error: jest.fn() },
+}));
+jest.mock('path', () => ({
+ resolve: jest.fn(),
+ join: jest.fn().mockReturnValue('/mock/path'),
+ relative: jest.fn().mockReturnValue('relative/path'),
+ extname: jest.fn().mockReturnValue('.png'),
+}));
+jest.mock('fs', () => ({
+ existsSync: jest.fn().mockReturnValue(true),
+ mkdirSync: jest.fn(),
+ promises: { writeFile: jest.fn(), readFile: jest.fn(), unlink: jest.fn() },
+}));
+
+const FAKE_BASE64 = 'aGVsbG8=';
+
+const makeToolCall = (name, args) => ({
+ id: 'call_test_123',
+ name,
+ args,
+ type: 'tool_call',
+});
+
+describe('image tools - agent mode ToolMessage format', () => {
+ const ENV_KEYS = ['DALLE_API_KEY', 'FLUX_API_KEY', 'SD_WEBUI_URL', 'PROXY'];
+ let savedEnv = {};
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ for (const key of ENV_KEYS) {
+ savedEnv[key] = process.env[key];
+ }
+ process.env.DALLE_API_KEY = 'test-dalle-key';
+ process.env.FLUX_API_KEY = 'test-flux-key';
+ process.env.SD_WEBUI_URL = 'http://localhost:7860';
+ delete process.env.PROXY;
+ });
+
+ afterEach(() => {
+ for (const key of ENV_KEYS) {
+ if (savedEnv[key] === undefined) {
+ delete process.env[key];
+ } else {
+ process.env[key] = savedEnv[key];
+ }
+ }
+ savedEnv = {};
+ });
+
+ describe('DALLE3', () => {
+ beforeEach(() => {
+ OpenAI.mockImplementation(() => ({
+ images: {
+ generate: jest.fn().mockResolvedValue({
+ data: [{ url: 'https://example.com/image.png' }],
+ }),
+ },
+ }));
+ undici.fetch.mockResolvedValue({
+ arrayBuffer: () => Promise.resolve(Buffer.from(FAKE_BASE64, 'base64')),
+ });
+ });
+
+ it('sets responseFormat to content_and_artifact when isAgent is true', () => {
+ const dalle = new DALLE3({ isAgent: true });
+ expect(dalle.responseFormat).toBe('content_and_artifact');
+ });
+
+ it('does not set responseFormat when isAgent is false', () => {
+ const dalle = new DALLE3({ isAgent: false, processFileURL: jest.fn() });
+ expect(dalle.responseFormat).not.toBe('content_and_artifact');
+ });
+
+ it('invoke() returns ToolMessage with base64 in artifact, not serialized in content', async () => {
+ const dalle = new DALLE3({ isAgent: true });
+ const result = await dalle.invoke(
+ makeToolCall('dalle', {
+ prompt: 'a box',
+ quality: 'standard',
+ size: '1024x1024',
+ style: 'vivid',
+ }),
+ );
+
+ expect(result).toBeInstanceOf(ToolMessage);
+
+ const contentStr =
+ typeof result.content === 'string' ? result.content : JSON.stringify(result.content);
+ expect(contentStr).not.toContain(FAKE_BASE64);
+
+ expect(result.artifact).toBeDefined();
+ const artifactContent = result.artifact?.content;
+ expect(Array.isArray(artifactContent)).toBe(true);
+ expect(artifactContent[0].type).toBe(ContentTypes.IMAGE_URL);
+ expect(artifactContent[0].image_url.url).toContain('base64');
+ });
+
+ it('invoke() returns ToolMessage with error string in content when API fails', async () => {
+ OpenAI.mockImplementation(() => ({
+ images: { generate: jest.fn().mockRejectedValue(new Error('API error')) },
+ }));
+
+ const dalle = new DALLE3({ isAgent: true });
+ const result = await dalle.invoke(
+ makeToolCall('dalle', {
+ prompt: 'a box',
+ quality: 'standard',
+ size: '1024x1024',
+ style: 'vivid',
+ }),
+ );
+
+ expect(result).toBeInstanceOf(ToolMessage);
+ const contentStr =
+ typeof result.content === 'string' ? result.content : JSON.stringify(result.content);
+ expect(contentStr).toContain('Something went wrong');
+ expect(result.artifact).toBeDefined();
+ });
+ });
+
+ describe('FluxAPI', () => {
+ beforeEach(() => {
+ jest.useFakeTimers();
+ axios.post.mockResolvedValue({ data: { id: 'task-123' } });
+ axios.get.mockResolvedValue({
+ data: { status: 'Ready', result: { sample: 'https://example.com/image.png' } },
+ });
+ fetch.mockResolvedValue({
+ arrayBuffer: () => Promise.resolve(Buffer.from(FAKE_BASE64, 'base64')),
+ });
+ });
+
+ afterEach(() => {
+ jest.useRealTimers();
+ });
+
+ it('sets responseFormat to content_and_artifact when isAgent is true', () => {
+ const flux = new FluxAPI({ isAgent: true });
+ expect(flux.responseFormat).toBe('content_and_artifact');
+ });
+
+ it('does not set responseFormat when isAgent is false', () => {
+ const flux = new FluxAPI({ isAgent: false, processFileURL: jest.fn() });
+ expect(flux.responseFormat).not.toBe('content_and_artifact');
+ });
+
+ it('invoke() returns ToolMessage with base64 in artifact, not serialized in content', async () => {
+ const flux = new FluxAPI({ isAgent: true });
+ const invokePromise = flux.invoke(
+ makeToolCall('flux', { prompt: 'a box', endpoint: '/v1/flux-dev' }),
+ );
+ await jest.runAllTimersAsync();
+ const result = await invokePromise;
+
+ expect(result).toBeInstanceOf(ToolMessage);
+ const contentStr =
+ typeof result.content === 'string' ? result.content : JSON.stringify(result.content);
+ expect(contentStr).not.toContain(FAKE_BASE64);
+
+ expect(result.artifact).toBeDefined();
+ const artifactContent = result.artifact?.content;
+ expect(Array.isArray(artifactContent)).toBe(true);
+ expect(artifactContent[0].type).toBe(ContentTypes.IMAGE_URL);
+ expect(artifactContent[0].image_url.url).toContain('base64');
+ });
+
+ it('invoke() returns ToolMessage with base64 in artifact for generate_finetuned action', async () => {
+ const flux = new FluxAPI({ isAgent: true });
+ const invokePromise = flux.invoke(
+ makeToolCall('flux', {
+ action: 'generate_finetuned',
+ prompt: 'a box',
+ finetune_id: 'ft-abc123',
+ endpoint: '/v1/flux-pro-finetuned',
+ }),
+ );
+ await jest.runAllTimersAsync();
+ const result = await invokePromise;
+
+ expect(result).toBeInstanceOf(ToolMessage);
+ const contentStr =
+ typeof result.content === 'string' ? result.content : JSON.stringify(result.content);
+ expect(contentStr).not.toContain(FAKE_BASE64);
+
+ expect(result.artifact).toBeDefined();
+ const artifactContent = result.artifact?.content;
+ expect(Array.isArray(artifactContent)).toBe(true);
+ expect(artifactContent[0].type).toBe(ContentTypes.IMAGE_URL);
+ expect(artifactContent[0].image_url.url).toContain('base64');
+ });
+
+ it('invoke() returns ToolMessage with error string in content when task submission fails', async () => {
+ axios.post.mockRejectedValue(new Error('Network error'));
+
+ const flux = new FluxAPI({ isAgent: true });
+ const invokePromise = flux.invoke(
+ makeToolCall('flux', { prompt: 'a box', endpoint: '/v1/flux-dev' }),
+ );
+ await jest.runAllTimersAsync();
+ const result = await invokePromise;
+
+ expect(result).toBeInstanceOf(ToolMessage);
+ const contentStr =
+ typeof result.content === 'string' ? result.content : JSON.stringify(result.content);
+ expect(contentStr).toContain('Something went wrong');
+ expect(result.artifact).toBeDefined();
+ });
+ });
+
+ describe('StableDiffusion', () => {
+ beforeEach(() => {
+ axios.post.mockResolvedValue({
+ data: {
+ images: [FAKE_BASE64],
+ info: JSON.stringify({ height: 1024, width: 1024, seed: 42, infotexts: [] }),
+ },
+ });
+ });
+
+ it('sets responseFormat to content_and_artifact when isAgent is true', () => {
+ const sd = new StableDiffusionAPI({ isAgent: true, override: true });
+ expect(sd.responseFormat).toBe('content_and_artifact');
+ });
+
+ it('does not set responseFormat when isAgent is false', () => {
+ const sd = new StableDiffusionAPI({
+ isAgent: false,
+ override: true,
+ uploadImageBuffer: jest.fn(),
+ });
+ expect(sd.responseFormat).not.toBe('content_and_artifact');
+ });
+
+ it('invoke() returns ToolMessage with base64 in artifact, not serialized in content', async () => {
+ const sd = new StableDiffusionAPI({ isAgent: true, override: true, userId: 'user-1' });
+ const result = await sd.invoke(
+ makeToolCall('stable-diffusion', { prompt: 'a box', negative_prompt: '' }),
+ );
+
+ expect(result).toBeInstanceOf(ToolMessage);
+ const contentStr =
+ typeof result.content === 'string' ? result.content : JSON.stringify(result.content);
+ expect(contentStr).not.toContain(FAKE_BASE64);
+
+ expect(result.artifact).toBeDefined();
+ const artifactContent = result.artifact?.content;
+ expect(Array.isArray(artifactContent)).toBe(true);
+ expect(artifactContent[0].type).toBe(ContentTypes.IMAGE_URL);
+ expect(artifactContent[0].image_url.url).toContain('base64');
+ });
+
+ it('invoke() returns ToolMessage with error string in content when API fails', async () => {
+ axios.post.mockRejectedValue(new Error('Connection refused'));
+
+ const sd = new StableDiffusionAPI({ isAgent: true, override: true, userId: 'user-1' });
+ const result = await sd.invoke(
+ makeToolCall('stable-diffusion', { prompt: 'a box', negative_prompt: '' }),
+ );
+
+ expect(result).toBeInstanceOf(ToolMessage);
+ const contentStr =
+ typeof result.content === 'string' ? result.content : JSON.stringify(result.content);
+ expect(contentStr).toContain('Error making API request');
+ });
+ });
+});
From 7e74165c3cc6841253378b2ebde1057caa13d80c Mon Sep 17 00:00:00 2001
From: Pol Burkardt Freire
Date: Thu, 19 Mar 2026 20:49:52 +0100
Subject: [PATCH 07/98] =?UTF-8?q?=F0=9F=93=96=20feat:=20Add=20Native=20ODT?=
=?UTF-8?q?=20Document=20Parser=20Support=20(#12303)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* fix: add ODT support to native document parser
* fix: replace execSync with jszip for ODT parsing
* docs: update documentParserMimeTypes comment to include odt
* fix: improve ODT XML extraction and add empty.odt fixture
- Scope extraction to to exclude metadata/style nodes
- Map and closings to newlines, preserving paragraph
structure instead of collapsing everything to a single line
- Handle as explicit newlines
- Strip remaining tags, normalize horizontal whitespace, cap consecutive
blank lines at one
- Regenerate sample.odt as a two-paragraph fixture so the test exercises
multi-paragraph output
- Add empty.odt fixture and test asserting 'No text found in document'
* fix: address review findings in ODT parser
- Use static `import JSZip from 'jszip'` instead of dynamic import;
jszip is CommonJS-only with no ESM/Jest-isolation concern (F1)
- Decode the five standard XML entities after tag-stripping so
documents with &, <, >, ", ' send correct text to the LLM (F2)
- Remove @types/jszip devDependency; jszip ships bundled declarations
and @types/jszip is a stale 2020 stub that would shadow them (F3)
- Handle → \t and → ' ' before the generic
tag stripper so tab-aligned and multi-space content is preserved (F4)
- Add sample-entities.odt fixture and test covering entity decoding,
tab, and spacing-element handling (F5)
- Rename 'throws for empty odt' → 'throws for odt with no extractable
text' to distinguish from a zero-byte/corrupt file case (F8)
* fix: add decompressed content size cap to odtToText (F6)
Reads uncompressed entry sizes from the JSZip internal metadata before
extracting any content. Throws if the total exceeds 50MB, preventing a
crafted ODT with a high-ratio compressed payload from exhausting heap.
Adds a corresponding test using a real DEFLATE-compressed ZIP (~51KB on
disk, 51MB uncompressed) to verify the guard fires before any extraction.
* fix: add java to codeTypeMapping for file upload support
.java files were rejected with "Unable to determine file type" because
browsers send an empty MIME type for them and codeTypeMapping had no
'java' entry for inferMimeType() to fall back on.
text/x-java was already present in all five validation lists
(fullMimeTypesList, codeInterpreterMimeTypesList, retrievalMimeTypesList,
textMimeTypes, retrievalMimeTypes), so mapping to it (not text/plain)
ensures .java uploads work for both File Search and Code Interpreter.
Closes #12307
* fix: address follow-up review findings (A-E)
A: regenerate package-lock.json after removing @types/jszip from
package.json; without this npm ci was still installing the stale
2020 type stubs and TypeScript was resolving against them
B: replace dynamic import('jszip') in the zip-bomb test with the same
static import already used in production; jszip is CJS-only with no
ESM/Jest isolation concern
C: document that the _data.uncompressedSize guard fails open if jszip
renames the private field (accepted limitation, test would catch it)
D: rename 'preserves tabs' test to 'normalizes tab and spacing elements
to spaces' since is collapsed to a space, not kept as \t
E: fix test.each([ formatting artifact (missing newline after '[')
---------
Co-authored-by: Danny Avila
---
package-lock.json | 3 +
packages/api/package.json | 3 +
packages/api/src/files/documents/crud.spec.ts | 68 ++++++++++++++++++
packages/api/src/files/documents/crud.ts | 58 +++++++++++++++
packages/api/src/files/documents/empty.odt | Bin 0 -> 1206 bytes
.../src/files/documents/sample-entities.odt | Bin 0 -> 1291 bytes
packages/api/src/files/documents/sample.odt | Bin 0 -> 1348 bytes
.../data-provider/src/file-config.spec.ts | 3 +-
packages/data-provider/src/file-config.ts | 4 +-
9 files changed, 137 insertions(+), 2 deletions(-)
create mode 100644 packages/api/src/files/documents/empty.odt
create mode 100644 packages/api/src/files/documents/sample-entities.odt
create mode 100644 packages/api/src/files/documents/sample.odt
diff --git a/package-lock.json b/package-lock.json
index 2454745a79..a056cc32ef 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -43789,6 +43789,9 @@
"name": "@librechat/api",
"version": "1.7.26",
"license": "ISC",
+ "dependencies": {
+ "jszip": "^3.10.1"
+ },
"devDependencies": {
"@babel/preset-env": "^7.21.5",
"@babel/preset-react": "^7.18.6",
diff --git a/packages/api/package.json b/packages/api/package.json
index 42f1f0e9f0..57675ee371 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -119,5 +119,8 @@
"rate-limit-redis": "^4.2.0",
"undici": "^7.24.1",
"zod": "^3.22.4"
+ },
+ "dependencies": {
+ "jszip": "^3.10.1"
}
}
diff --git a/packages/api/src/files/documents/crud.spec.ts b/packages/api/src/files/documents/crud.spec.ts
index f8b255dd5e..a1c317279c 100644
--- a/packages/api/src/files/documents/crud.spec.ts
+++ b/packages/api/src/files/documents/crud.spec.ts
@@ -1,4 +1,6 @@
import path from 'path';
+import * as fs from 'fs';
+import JSZip from 'jszip';
import { parseDocument } from './crud';
describe('Document Parser', () => {
@@ -74,6 +76,72 @@ describe('Document Parser', () => {
});
});
+ test('parseDocument() parses text from odt', async () => {
+ const file = {
+ originalname: 'sample.odt',
+ path: path.join(__dirname, 'sample.odt'),
+ mimetype: 'application/vnd.oasis.opendocument.text',
+ } as Express.Multer.File;
+
+ const document = await parseDocument({ file });
+
+ expect(document).toEqual({
+ bytes: 50,
+ filename: 'sample.odt',
+ filepath: 'document_parser',
+ images: [],
+ text: 'This is a sample ODT file.\n\nIt has two paragraphs.',
+ });
+ });
+
+ test('parseDocument() throws for odt with no extractable text', async () => {
+ const file = {
+ originalname: 'empty.odt',
+ path: path.join(__dirname, 'empty.odt'),
+ mimetype: 'application/vnd.oasis.opendocument.text',
+ } as Express.Multer.File;
+
+ await expect(parseDocument({ file })).rejects.toThrow('No text found in document');
+ });
+
+ test('parseDocument() throws for odt whose decompressed content exceeds the size limit', async () => {
+ const zip = new JSZip();
+ zip.file('mimetype', 'application/vnd.oasis.opendocument.text', { compression: 'STORE' });
+ zip.file('content.xml', 'x'.repeat(51 * 1024 * 1024), { compression: 'DEFLATE' });
+ const buf = await zip.generateAsync({ type: 'nodebuffer' });
+
+ const tmpPath = path.join(__dirname, 'bomb.odt');
+ await fs.promises.writeFile(tmpPath, buf);
+ try {
+ const file = {
+ originalname: 'bomb.odt',
+ path: tmpPath,
+ mimetype: 'application/vnd.oasis.opendocument.text',
+ } as Express.Multer.File;
+ await expect(parseDocument({ file })).rejects.toThrow(/exceeds the 50MB limit/);
+ } finally {
+ await fs.promises.unlink(tmpPath);
+ }
+ });
+
+ test('parseDocument() decodes XML entities and normalizes tab and spacing elements to spaces from odt', async () => {
+ const file = {
+ originalname: 'sample-entities.odt',
+ path: path.join(__dirname, 'sample-entities.odt'),
+ mimetype: 'application/vnd.oasis.opendocument.text',
+ } as Express.Multer.File;
+
+ const document = await parseDocument({ file });
+
+ expect(document).toEqual({
+ bytes: 19,
+ filename: 'sample-entities.odt',
+ filepath: 'document_parser',
+ images: [],
+ text: 'AT&T and A>B\n\nx y z',
+ });
+ });
+
test.each([
'application/msexcel',
'application/x-msexcel',
diff --git a/packages/api/src/files/documents/crud.ts b/packages/api/src/files/documents/crud.ts
index 61c1956542..e255323f77 100644
--- a/packages/api/src/files/documents/crud.ts
+++ b/packages/api/src/files/documents/crud.ts
@@ -1,4 +1,5 @@
import * as fs from 'fs';
+import JSZip from 'jszip';
import { megabyte, excelMimeTypes, FileSources } from 'librechat-data-provider';
import type { TextItem } from 'pdfjs-dist/types/src/display/api';
import type { MistralOCRUploadResult } from '~/types';
@@ -6,6 +7,7 @@ import type { MistralOCRUploadResult } from '~/types';
type FileParseFn = (file: Express.Multer.File) => Promise;
const DOCUMENT_PARSER_MAX_FILE_SIZE = 15 * megabyte;
+const ODT_MAX_DECOMPRESSED_SIZE = 50 * megabyte;
/**
* Parses an uploaded document and extracts its text content and metadata.
@@ -61,6 +63,9 @@ function getParserForMimeType(mimetype: string): FileParseFn | undefined {
) {
return excelSheetToText;
}
+ if (mimetype === 'application/vnd.oasis.opendocument.text') {
+ return odtToText;
+ }
return undefined;
}
@@ -110,3 +115,56 @@ async function excelSheetToText(file: Express.Multer.File): Promise {
return text;
}
+
+/**
+ * Parses OpenDocument Text (.odt) by extracting the body text from content.xml.
+ * Uses regex-based XML extraction scoped to : paragraph/heading
+ * boundaries become newlines, tab and spacing elements are preserved, and the
+ * five standard XML entities are decoded. Complex elements such as frames,
+ * text boxes, and annotations are stripped without replacement.
+ */
+async function odtToText(file: Express.Multer.File): Promise {
+ const data = await fs.promises.readFile(file.path);
+ const zip = await JSZip.loadAsync(data);
+
+ let totalUncompressed = 0;
+ zip.forEach((_, entry) => {
+ const raw = entry as JSZip.JSZipObject & { _data?: { uncompressedSize?: number } };
+ // _data.uncompressedSize is populated from the ZIP central directory at parse time
+ // by jszip (private internal, jszip@3.x). If the field is absent the guard fails
+ // open (adds 0); this is an accepted limitation of the approach.
+ totalUncompressed += raw._data?.uncompressedSize ?? 0;
+ });
+ if (totalUncompressed > ODT_MAX_DECOMPRESSED_SIZE) {
+ throw new Error(
+ `ODT file decompressed content (${Math.ceil(totalUncompressed / megabyte)}MB) exceeds the ${ODT_MAX_DECOMPRESSED_SIZE / megabyte}MB limit`,
+ );
+ }
+
+ const contentFile = zip.file('content.xml');
+ if (!contentFile) {
+ throw new Error('ODT file is missing content.xml');
+ }
+ const xml = await contentFile.async('string');
+ const bodyMatch = xml.match(/]*>([\s\S]*?)<\/office:body>/);
+ if (!bodyMatch) {
+ return '';
+ }
+ return bodyMatch[1]
+ .replace(/<\/text:p>/g, '\n')
+ .replace(/<\/text:h>/g, '\n')
+ .replace(//g, '\n')
+ .replace(//g, '\t')
+ .replace(/]*\/>/g, ' ')
+ .replace(/<[^>]+>/g, '')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/&/g, '&')
+ .replace(/"/g, '"')
+ .replace(/'/g, "'")
+ .replace(/[ \t]+/g, ' ')
+ .replace(/\n[ \t]+/g, '\n')
+ .replace(/[ \t]+\n/g, '\n')
+ .replace(/\n{3,}/g, '\n\n')
+ .trim();
+}
diff --git a/packages/api/src/files/documents/empty.odt b/packages/api/src/files/documents/empty.odt
new file mode 100644
index 0000000000000000000000000000000000000000..348d78ba0878f468473056e9d49f4db553dbbaa2
GIT binary patch
literal 1206
zcmb7Ey-ve05DveB!oUDCkr$>k{4B_7S|I@vLZA+?Ay7zMFiPyGb}RY@z+3PXEQpae
z;0a*i9T+$#apDAliX%67=kL3_+@0G!oSJS}YCYVGv-8)@#Wj%Co`W6Jn8;B={3MBJ
z;7iJxJ7i`+|xlPY4TnFo+40O-XKLx8iK|<7W3_!m`v}0A~SbQXy~SsMUcVdr0~M
zJbGxOxsGhY0v=L!<)PD)ePRUD
zHEg1B8Y{D?l*eT&Y{t!VGLqjy?S^gZWc`8UF_bEBgd9agxaQ#{4@XKb;mUDl0b3d+
zNg1HIc_=@r$D#jfewd)*soWZ4gCH;>&9fxu`T(yp-mcgw%J`*n4Qq
z!ADt>%Yc6;VcY~G9W$PMjdn;+2!}_`R98nXzXlJEO6u$D=Fx;p4Z1y~kkdc?TxX
zI8#;0Xj$f2LRD4>av{=5L1iW@vciKIv~aUS9K^JD)7K8nX-u~=jY3v3d<34wquWMFn9yjqH&
zRul-znG94CxY7a_9n`suM@!z6t+9sNXmZ3q$MF~;E0(TQ!QoRSzEoAtByl&-inP+}
z4z2?m5BTaVWjvklQ(hj<5fv$#?w_l}mmz8PYH|l%8;+u9B(97tQ4LKZe1J*(xz7wq
zqu=Ij9Y$Ka9Deh-UtFkXJ9fc3eKnu@uiqS#KJ3fa>mQd;e?1?%Fiu6bVDi7Z!Eyp?
zG<{%~o7U3eO+DMypA~Jl7Fo`G=-p+7I^QbK{gSFr5hl2E_o8%e&eDwf`Yc6w!_x%C
z({0ry{I>|_csX+1yt?EBT3-haNgLN1P~olZ2j1UrgSOzcVef_^Yk2HC)+v~d$|11>
tdY*16Gw#9b$#$>7`YMMlW{IBn)}`5hsjr{e$*tQOHiF+zc<+t$>NlRrc)I`q
literal 0
HcmV?d00001
diff --git a/packages/api/src/files/documents/sample.odt b/packages/api/src/files/documents/sample.odt
new file mode 100644
index 0000000000000000000000000000000000000000..e6c49d0f29350dc38ab55a72ef21906027b8607d
GIT binary patch
literal 1348
zcmb7EO>WdM6i&;4U|_)lmPnR2tdj7vK~^#q60KBKg;@+6gpf?)=~zl^EB3UV10VsX
zfO}M3_6j`!D;C^==Oj*?Akg3$C(qB{_xw5U+4G|tlWAbvr!V^bhpT7z9)oTBZRjO0
znb{~tl`42f4VNN56?r77<~ov!iCkvuk_i(Tc5dc_VAtQ?J^Hm11h(IWV3`!gBG1br
z*@FiL;FAhjYQ
zXvs7&8S$kNA5H4D8jqE{E!$#!t~Q&3ZA-4f4ZGcuxAo_EHbuy7m9C8};X`L(P13PH
z`BCN@JMQ6DR^tI*tx?SuE3OgzDblo51w(IMEzpt|EK1@QQpe~ShX$>vM&?W+MJswz
zQMJ+|bP~7H-IAT7QPmB{$(jwT0t%sPcfdN{U%U(tUmwIn*u~}dAD^LcY=7v(N-Fr0
z>HjJt$05#W3&;((&7;FNsq997CbOHepFM=4uQsUnt&1X5)T~k*U3!zw@Z6(&5v&0vakvKe!_cyVn@FLbE=ah
literal 0
HcmV?d00001
diff --git a/packages/data-provider/src/file-config.spec.ts b/packages/data-provider/src/file-config.spec.ts
index 0ab9f23a3e..96f48621cc 100644
--- a/packages/data-provider/src/file-config.spec.ts
+++ b/packages/data-provider/src/file-config.spec.ts
@@ -31,6 +31,7 @@ describe('inferMimeType', () => {
expect(inferMimeType('test.py', '')).toBe('text/x-python');
expect(inferMimeType('code.js', '')).toBe('text/javascript');
expect(inferMimeType('photo.heic', '')).toBe('image/heic');
+ expect(inferMimeType('Main.java', '')).toBe('text/x-java');
});
it('should return empty string for unknown extension with no browser type', () => {
@@ -141,12 +142,12 @@ describe('documentParserMimeTypes', () => {
'application/x-msexcel',
'application/x-ms-excel',
'application/vnd.oasis.opendocument.spreadsheet',
+ 'application/vnd.oasis.opendocument.text',
])('matches natively parseable type: %s', (mimeType) => {
expect(check(mimeType)).toBe(true);
});
it.each([
- 'application/vnd.oasis.opendocument.text',
'application/vnd.oasis.opendocument.presentation',
'application/vnd.oasis.opendocument.graphics',
'text/plain',
diff --git a/packages/data-provider/src/file-config.ts b/packages/data-provider/src/file-config.ts
index 67b4197958..32a1a28cc9 100644
--- a/packages/data-provider/src/file-config.ts
+++ b/packages/data-provider/src/file-config.ts
@@ -202,12 +202,13 @@ export const defaultOCRMimeTypes = [
/^application\/vnd\.oasis\.opendocument\.(text|spreadsheet|presentation|graphics)$/,
];
-/** MIME types handled by the built-in document parser (pdf, docx, excel variants, ods) */
+/** MIME types handled by the built-in document parser (pdf, docx, excel variants, ods/odt) */
export const documentParserMimeTypes = [
excelMimeTypes,
/^application\/pdf$/,
/^application\/vnd\.openxmlformats-officedocument\.wordprocessingml\.document$/,
/^application\/vnd\.oasis\.opendocument\.spreadsheet$/,
+ /^application\/vnd\.oasis\.opendocument\.text$/,
];
export const defaultTextMimeTypes = [/^[\w.-]+\/[\w.-]+$/];
@@ -242,6 +243,7 @@ export const codeTypeMapping: { [key: string]: string } = {
py: 'text/x-python', // .py - Python source
rb: 'text/x-ruby', // .rb - Ruby source
tex: 'text/x-tex', // .tex - LaTeX source
+ java: 'text/x-java', // .java - Java source
js: 'text/javascript', // .js - JavaScript source
sh: 'application/x-sh', // .sh - Shell script
ts: 'application/typescript', // .ts - TypeScript source
From 39f5f83a8a2a3cbc6e86ccdd50dd027fa2d5e156 Mon Sep 17 00:00:00 2001
From: Danny Avila
Date: Thu, 19 Mar 2026 16:16:57 -0400
Subject: [PATCH 08/98] =?UTF-8?q?=F0=9F=94=8C=20fix:=20Isolate=20Code-Serv?=
=?UTF-8?q?er=20HTTP=20Agents=20to=20Prevent=20Socket=20Pool=20Contaminati?=
=?UTF-8?q?on=20(#12311)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* 🔧 fix: Isolate HTTP agents for code-server axios requests
Prevents socket hang up after 5s on Node 19+ when code executor has
file attachments. follow-redirects (axios dep) leaks `socket.destroy`
as a timeout listener on TCP sockets; with Node 19+ defaulting to
keepAlive: true, tainted sockets re-enter the global pool and destroy
active node-fetch requests in CodeExecutor after the idle timeout.
Uses dedicated http/https agents with keepAlive: false for all axios
calls targeting CODE_BASEURL in crud.js and process.js.
Closes #12298
* ♻️ refactor: Extract code-server HTTP agents to shared module
- Move duplicated agent construction from crud.js and process.js into
a shared agents.js module to eliminate DRY violation
- Switch process.js from raw `require('axios')` to `createAxiosInstance()`
for proxy configuration parity with crud.js
- Fix import ordering in process.js (agent constants no longer split imports)
- Add 120s timeout to uploadCodeEnvFile (was the only code-server call
without a timeout)
* ✅ test: Add regression tests for code-server socket isolation
- Add crud.spec.js covering getCodeOutputDownloadStream and
uploadCodeEnvFile (agent options, timeout, URL, error handling)
- Add socket pool isolation tests to process.spec.js asserting
keepAlive:false agents are forwarded to axios
- Update process.spec.js mocks for createAxiosInstance() migration
* ♻️ refactor: Move code-server agents to packages/api
Relocate agents.js from api/server/services/Files/Code/ to
packages/api/src/utils/code.ts per workspace conventions. Consumers
now import codeServerHttpAgent/codeServerHttpsAgent from @librechat/api.
---
.../Code/__tests__/process-traversal.spec.js | 28 ++--
api/server/services/Files/Code/crud.js | 12 +-
api/server/services/Files/Code/crud.spec.js | 149 ++++++++++++++++++
api/server/services/Files/Code/process.js | 17 +-
.../services/Files/Code/process.spec.js | 100 ++++++++----
packages/api/src/utils/code.ts | 11 ++
packages/api/src/utils/index.ts | 1 +
7 files changed, 273 insertions(+), 45 deletions(-)
create mode 100644 api/server/services/Files/Code/crud.spec.js
create mode 100644 packages/api/src/utils/code.ts
diff --git a/api/server/services/Files/Code/__tests__/process-traversal.spec.js b/api/server/services/Files/Code/__tests__/process-traversal.spec.js
index 2db366d06b..0b8548445d 100644
--- a/api/server/services/Files/Code/__tests__/process-traversal.spec.js
+++ b/api/server/services/Files/Code/__tests__/process-traversal.spec.js
@@ -10,11 +10,23 @@ jest.mock('@librechat/agents', () => ({
const mockSanitizeFilename = jest.fn();
-jest.mock('@librechat/api', () => ({
- logAxiosError: jest.fn(),
- getBasePath: jest.fn(() => ''),
- sanitizeFilename: mockSanitizeFilename,
-}));
+const mockAxios = jest.fn().mockResolvedValue({
+ data: Buffer.from('file-content'),
+});
+mockAxios.post = jest.fn();
+
+jest.mock('@librechat/api', () => {
+ const http = require('http');
+ const https = require('https');
+ return {
+ logAxiosError: jest.fn(),
+ getBasePath: jest.fn(() => ''),
+ sanitizeFilename: mockSanitizeFilename,
+ createAxiosInstance: jest.fn(() => mockAxios),
+ codeServerHttpAgent: new http.Agent({ keepAlive: false }),
+ codeServerHttpsAgent: new https.Agent({ keepAlive: false }),
+ };
+});
jest.mock('librechat-data-provider', () => ({
...jest.requireActual('librechat-data-provider'),
@@ -53,12 +65,6 @@ jest.mock('~/server/utils', () => ({
determineFileType: jest.fn().mockResolvedValue({ mime: 'text/csv' }),
}));
-jest.mock('axios', () =>
- jest.fn().mockResolvedValue({
- data: Buffer.from('file-content'),
- }),
-);
-
const { createFile } = require('~/models');
const { processCodeOutput } = require('../process');
diff --git a/api/server/services/Files/Code/crud.js b/api/server/services/Files/Code/crud.js
index 4781219fcf..945aec787b 100644
--- a/api/server/services/Files/Code/crud.js
+++ b/api/server/services/Files/Code/crud.js
@@ -1,6 +1,11 @@
const FormData = require('form-data');
const { getCodeBaseURL } = require('@librechat/agents');
-const { createAxiosInstance, logAxiosError } = require('@librechat/api');
+const {
+ logAxiosError,
+ createAxiosInstance,
+ codeServerHttpAgent,
+ codeServerHttpsAgent,
+} = require('@librechat/api');
const axios = createAxiosInstance();
@@ -25,6 +30,8 @@ async function getCodeOutputDownloadStream(fileIdentifier, apiKey) {
'User-Agent': 'LibreChat/1.0',
'X-API-Key': apiKey,
},
+ httpAgent: codeServerHttpAgent,
+ httpsAgent: codeServerHttpsAgent,
timeout: 15000,
};
@@ -69,6 +76,9 @@ async function uploadCodeEnvFile({ req, stream, filename, apiKey, entity_id = ''
'User-Id': req.user.id,
'X-API-Key': apiKey,
},
+ httpAgent: codeServerHttpAgent,
+ httpsAgent: codeServerHttpsAgent,
+ timeout: 120000,
maxContentLength: MAX_FILE_SIZE,
maxBodyLength: MAX_FILE_SIZE,
};
diff --git a/api/server/services/Files/Code/crud.spec.js b/api/server/services/Files/Code/crud.spec.js
new file mode 100644
index 0000000000..261f0f052b
--- /dev/null
+++ b/api/server/services/Files/Code/crud.spec.js
@@ -0,0 +1,149 @@
+const http = require('http');
+const https = require('https');
+const { Readable } = require('stream');
+
+const mockAxios = jest.fn();
+mockAxios.post = jest.fn();
+
+jest.mock('@librechat/agents', () => ({
+ getCodeBaseURL: jest.fn(() => 'https://code-api.example.com'),
+}));
+
+jest.mock('@librechat/api', () => {
+ const http = require('http');
+ const https = require('https');
+ return {
+ logAxiosError: jest.fn(({ message }) => message),
+ createAxiosInstance: jest.fn(() => mockAxios),
+ codeServerHttpAgent: new http.Agent({ keepAlive: false }),
+ codeServerHttpsAgent: new https.Agent({ keepAlive: false }),
+ };
+});
+
+const { codeServerHttpAgent, codeServerHttpsAgent } = require('@librechat/api');
+const { getCodeOutputDownloadStream, uploadCodeEnvFile } = require('./crud');
+
+describe('Code CRUD', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('getCodeOutputDownloadStream', () => {
+ it('should pass dedicated keepAlive:false agents to axios', async () => {
+ const mockResponse = { data: Readable.from(['chunk']) };
+ mockAxios.mockResolvedValue(mockResponse);
+
+ await getCodeOutputDownloadStream('session-1/file-1', 'test-key');
+
+ const callConfig = mockAxios.mock.calls[0][0];
+ expect(callConfig.httpAgent).toBe(codeServerHttpAgent);
+ expect(callConfig.httpsAgent).toBe(codeServerHttpsAgent);
+ expect(callConfig.httpAgent).toBeInstanceOf(http.Agent);
+ expect(callConfig.httpsAgent).toBeInstanceOf(https.Agent);
+ expect(callConfig.httpAgent.keepAlive).toBe(false);
+ expect(callConfig.httpsAgent.keepAlive).toBe(false);
+ });
+
+ it('should request stream response from the correct URL', async () => {
+ mockAxios.mockResolvedValue({ data: Readable.from(['chunk']) });
+
+ await getCodeOutputDownloadStream('session-1/file-1', 'test-key');
+
+ const callConfig = mockAxios.mock.calls[0][0];
+ expect(callConfig.url).toBe('https://code-api.example.com/download/session-1/file-1');
+ expect(callConfig.responseType).toBe('stream');
+ expect(callConfig.timeout).toBe(15000);
+ expect(callConfig.headers['X-API-Key']).toBe('test-key');
+ });
+
+ it('should throw on network error', async () => {
+ mockAxios.mockRejectedValue(new Error('ECONNREFUSED'));
+
+ await expect(getCodeOutputDownloadStream('s/f', 'key')).rejects.toThrow();
+ });
+ });
+
+ describe('uploadCodeEnvFile', () => {
+ const baseUploadParams = {
+ req: { user: { id: 'user-123' } },
+ stream: Readable.from(['file-content']),
+ filename: 'data.csv',
+ apiKey: 'test-key',
+ };
+
+ it('should pass dedicated keepAlive:false agents to axios', async () => {
+ mockAxios.post.mockResolvedValue({
+ data: {
+ message: 'success',
+ session_id: 'sess-1',
+ files: [{ fileId: 'fid-1', filename: 'data.csv' }],
+ },
+ });
+
+ await uploadCodeEnvFile(baseUploadParams);
+
+ const callConfig = mockAxios.post.mock.calls[0][2];
+ expect(callConfig.httpAgent).toBe(codeServerHttpAgent);
+ expect(callConfig.httpsAgent).toBe(codeServerHttpsAgent);
+ expect(callConfig.httpAgent).toBeInstanceOf(http.Agent);
+ expect(callConfig.httpsAgent).toBeInstanceOf(https.Agent);
+ expect(callConfig.httpAgent.keepAlive).toBe(false);
+ expect(callConfig.httpsAgent.keepAlive).toBe(false);
+ });
+
+ it('should set a timeout on upload requests', async () => {
+ mockAxios.post.mockResolvedValue({
+ data: {
+ message: 'success',
+ session_id: 'sess-1',
+ files: [{ fileId: 'fid-1', filename: 'data.csv' }],
+ },
+ });
+
+ await uploadCodeEnvFile(baseUploadParams);
+
+ const callConfig = mockAxios.post.mock.calls[0][2];
+ expect(callConfig.timeout).toBe(120000);
+ });
+
+ it('should return fileIdentifier on success', async () => {
+ mockAxios.post.mockResolvedValue({
+ data: {
+ message: 'success',
+ session_id: 'sess-1',
+ files: [{ fileId: 'fid-1', filename: 'data.csv' }],
+ },
+ });
+
+ const result = await uploadCodeEnvFile(baseUploadParams);
+ expect(result).toBe('sess-1/fid-1');
+ });
+
+ it('should append entity_id query param when provided', async () => {
+ mockAxios.post.mockResolvedValue({
+ data: {
+ message: 'success',
+ session_id: 'sess-1',
+ files: [{ fileId: 'fid-1', filename: 'data.csv' }],
+ },
+ });
+
+ const result = await uploadCodeEnvFile({ ...baseUploadParams, entity_id: 'agent-42' });
+ expect(result).toBe('sess-1/fid-1?entity_id=agent-42');
+ });
+
+ it('should throw when server returns non-success message', async () => {
+ mockAxios.post.mockResolvedValue({
+ data: { message: 'quota_exceeded', session_id: 's', files: [] },
+ });
+
+ await expect(uploadCodeEnvFile(baseUploadParams)).rejects.toThrow('quota_exceeded');
+ });
+
+ it('should throw on network error', async () => {
+ mockAxios.post.mockRejectedValue(new Error('ECONNREFUSED'));
+
+ await expect(uploadCodeEnvFile(baseUploadParams)).rejects.toThrow();
+ });
+ });
+});
diff --git a/api/server/services/Files/Code/process.js b/api/server/services/Files/Code/process.js
index e878b00255..7cdebeb202 100644
--- a/api/server/services/Files/Code/process.js
+++ b/api/server/services/Files/Code/process.js
@@ -1,9 +1,15 @@
const path = require('path');
const { v4 } = require('uuid');
-const axios = require('axios');
const { logger } = require('@librechat/data-schemas');
const { getCodeBaseURL } = require('@librechat/agents');
-const { logAxiosError, getBasePath, sanitizeFilename } = require('@librechat/api');
+const {
+ getBasePath,
+ logAxiosError,
+ sanitizeFilename,
+ createAxiosInstance,
+ codeServerHttpAgent,
+ codeServerHttpsAgent,
+} = require('@librechat/api');
const {
Tools,
megabyte,
@@ -23,6 +29,8 @@ const { getStrategyFunctions } = require('~/server/services/Files/strategies');
const { convertImage } = require('~/server/services/Files/images/convert');
const { determineFileType } = require('~/server/utils');
+const axios = createAxiosInstance();
+
/**
* Creates a fallback download URL response when file cannot be processed locally.
* Used when: file exceeds size limit, storage strategy unavailable, or download error occurs.
@@ -102,6 +110,8 @@ const processCodeOutput = async ({
'User-Agent': 'LibreChat/1.0',
'X-API-Key': apiKey,
},
+ httpAgent: codeServerHttpAgent,
+ httpsAgent: codeServerHttpsAgent,
timeout: 15000,
});
@@ -300,6 +310,8 @@ async function getSessionInfo(fileIdentifier, apiKey) {
'User-Agent': 'LibreChat/1.0',
'X-API-Key': apiKey,
},
+ httpAgent: codeServerHttpAgent,
+ httpsAgent: codeServerHttpsAgent,
timeout: 5000,
});
@@ -448,5 +460,6 @@ const primeFiles = async (options, apiKey) => {
module.exports = {
primeFiles,
+ getSessionInfo,
processCodeOutput,
};
diff --git a/api/server/services/Files/Code/process.spec.js b/api/server/services/Files/Code/process.spec.js
index b89a6c6307..a805ee2bcc 100644
--- a/api/server/services/Files/Code/process.spec.js
+++ b/api/server/services/Files/Code/process.spec.js
@@ -36,11 +36,24 @@ jest.mock('uuid', () => ({
v4: jest.fn(() => 'mock-uuid-1234'),
}));
-// Mock axios
-jest.mock('axios');
-const axios = require('axios');
+// Mock axios — process.js now uses createAxiosInstance() from @librechat/api
+const mockAxios = jest.fn();
+mockAxios.post = jest.fn();
+mockAxios.isAxiosError = jest.fn(() => false);
+
+jest.mock('@librechat/api', () => {
+ const http = require('http');
+ const https = require('https');
+ return {
+ logAxiosError: jest.fn(),
+ getBasePath: jest.fn(() => ''),
+ sanitizeFilename: jest.fn((name) => name),
+ createAxiosInstance: jest.fn(() => mockAxios),
+ codeServerHttpAgent: new http.Agent({ keepAlive: false }),
+ codeServerHttpsAgent: new https.Agent({ keepAlive: false }),
+ };
+});
-// Mock logger
jest.mock('@librechat/data-schemas', () => ({
logger: {
warn: jest.fn(),
@@ -49,18 +62,10 @@ jest.mock('@librechat/data-schemas', () => ({
},
}));
-// Mock getCodeBaseURL
jest.mock('@librechat/agents', () => ({
getCodeBaseURL: jest.fn(() => 'https://code-api.example.com'),
}));
-// Mock logAxiosError and getBasePath
-jest.mock('@librechat/api', () => ({
- logAxiosError: jest.fn(),
- getBasePath: jest.fn(() => ''),
- sanitizeFilename: jest.fn((name) => name),
-}));
-
// Mock models
const mockClaimCodeFile = jest.fn();
jest.mock('~/models', () => ({
@@ -90,14 +95,16 @@ jest.mock('~/server/utils', () => ({
determineFileType: jest.fn(),
}));
+const http = require('http');
+const https = require('https');
const { createFile, getFiles } = require('~/models');
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
const { convertImage } = require('~/server/services/Files/images/convert');
const { determineFileType } = require('~/server/utils');
const { logger } = require('@librechat/data-schemas');
+const { codeServerHttpAgent, codeServerHttpsAgent } = require('@librechat/api');
-// Import after mocks
-const { processCodeOutput } = require('./process');
+const { processCodeOutput, getSessionInfo } = require('./process');
describe('Code Process', () => {
const mockReq = {
@@ -145,7 +152,7 @@ describe('Code Process', () => {
});
const smallBuffer = Buffer.alloc(100);
- axios.mockResolvedValue({ data: smallBuffer });
+ mockAxios.mockResolvedValue({ data: smallBuffer });
const result = await processCodeOutput(baseParams);
@@ -168,7 +175,7 @@ describe('Code Process', () => {
});
const smallBuffer = Buffer.alloc(100);
- axios.mockResolvedValue({ data: smallBuffer });
+ mockAxios.mockResolvedValue({ data: smallBuffer });
const result = await processCodeOutput(baseParams);
@@ -182,7 +189,7 @@ describe('Code Process', () => {
it('should process image files using convertImage', async () => {
const imageParams = { ...baseParams, name: 'chart.png' };
const imageBuffer = Buffer.alloc(500);
- axios.mockResolvedValue({ data: imageBuffer });
+ mockAxios.mockResolvedValue({ data: imageBuffer });
const convertedFile = {
filepath: '/uploads/converted-image.webp',
@@ -212,7 +219,7 @@ describe('Code Process', () => {
});
const imageBuffer = Buffer.alloc(500);
- axios.mockResolvedValue({ data: imageBuffer });
+ mockAxios.mockResolvedValue({ data: imageBuffer });
convertImage.mockResolvedValue({ filepath: '/images/user-123/existing-img-id.webp' });
const result = await processCodeOutput(imageParams);
@@ -235,7 +242,7 @@ describe('Code Process', () => {
describe('non-image file processing', () => {
it('should process non-image files using saveBuffer', async () => {
const smallBuffer = Buffer.alloc(100);
- axios.mockResolvedValue({ data: smallBuffer });
+ mockAxios.mockResolvedValue({ data: smallBuffer });
const mockSaveBuffer = jest.fn().mockResolvedValue('/uploads/saved-file.txt');
getStrategyFunctions.mockReturnValue({ saveBuffer: mockSaveBuffer });
@@ -256,7 +263,7 @@ describe('Code Process', () => {
it('should detect MIME type from buffer', async () => {
const smallBuffer = Buffer.alloc(100);
- axios.mockResolvedValue({ data: smallBuffer });
+ mockAxios.mockResolvedValue({ data: smallBuffer });
determineFileType.mockResolvedValue({ mime: 'application/pdf' });
const result = await processCodeOutput({ ...baseParams, name: 'document.pdf' });
@@ -267,7 +274,7 @@ describe('Code Process', () => {
it('should fallback to application/octet-stream for unknown types', async () => {
const smallBuffer = Buffer.alloc(100);
- axios.mockResolvedValue({ data: smallBuffer });
+ mockAxios.mockResolvedValue({ data: smallBuffer });
determineFileType.mockResolvedValue(null);
const result = await processCodeOutput({ ...baseParams, name: 'unknown.xyz' });
@@ -282,7 +289,7 @@ describe('Code Process', () => {
fileSizeLimitConfig.value = 1000; // 1KB limit
const largeBuffer = Buffer.alloc(5000); // 5KB - exceeds 1KB limit
- axios.mockResolvedValue({ data: largeBuffer });
+ mockAxios.mockResolvedValue({ data: largeBuffer });
const result = await processCodeOutput(baseParams);
@@ -300,7 +307,7 @@ describe('Code Process', () => {
describe('fallback behavior', () => {
it('should fallback to download URL when saveBuffer is not available', async () => {
const smallBuffer = Buffer.alloc(100);
- axios.mockResolvedValue({ data: smallBuffer });
+ mockAxios.mockResolvedValue({ data: smallBuffer });
getStrategyFunctions.mockReturnValue({ saveBuffer: null });
const result = await processCodeOutput(baseParams);
@@ -313,7 +320,7 @@ describe('Code Process', () => {
});
it('should fallback to download URL on axios error', async () => {
- axios.mockRejectedValue(new Error('Network error'));
+ mockAxios.mockRejectedValue(new Error('Network error'));
const result = await processCodeOutput(baseParams);
@@ -327,7 +334,7 @@ describe('Code Process', () => {
describe('usage counter increment', () => {
it('should set usage to 1 for new files', async () => {
const smallBuffer = Buffer.alloc(100);
- axios.mockResolvedValue({ data: smallBuffer });
+ mockAxios.mockResolvedValue({ data: smallBuffer });
const result = await processCodeOutput(baseParams);
@@ -341,7 +348,7 @@ describe('Code Process', () => {
createdAt: '2024-01-01',
});
const smallBuffer = Buffer.alloc(100);
- axios.mockResolvedValue({ data: smallBuffer });
+ mockAxios.mockResolvedValue({ data: smallBuffer });
const result = await processCodeOutput(baseParams);
@@ -354,7 +361,7 @@ describe('Code Process', () => {
createdAt: '2024-01-01',
});
const smallBuffer = Buffer.alloc(100);
- axios.mockResolvedValue({ data: smallBuffer });
+ mockAxios.mockResolvedValue({ data: smallBuffer });
const result = await processCodeOutput(baseParams);
@@ -365,7 +372,7 @@ describe('Code Process', () => {
describe('metadata and file properties', () => {
it('should include fileIdentifier in metadata', async () => {
const smallBuffer = Buffer.alloc(100);
- axios.mockResolvedValue({ data: smallBuffer });
+ mockAxios.mockResolvedValue({ data: smallBuffer });
const result = await processCodeOutput(baseParams);
@@ -376,7 +383,7 @@ describe('Code Process', () => {
it('should set correct context for code-generated files', async () => {
const smallBuffer = Buffer.alloc(100);
- axios.mockResolvedValue({ data: smallBuffer });
+ mockAxios.mockResolvedValue({ data: smallBuffer });
const result = await processCodeOutput(baseParams);
@@ -385,7 +392,7 @@ describe('Code Process', () => {
it('should include toolCallId and messageId in result', async () => {
const smallBuffer = Buffer.alloc(100);
- axios.mockResolvedValue({ data: smallBuffer });
+ mockAxios.mockResolvedValue({ data: smallBuffer });
const result = await processCodeOutput(baseParams);
@@ -395,7 +402,7 @@ describe('Code Process', () => {
it('should call createFile with upsert enabled', async () => {
const smallBuffer = Buffer.alloc(100);
- axios.mockResolvedValue({ data: smallBuffer });
+ mockAxios.mockResolvedValue({ data: smallBuffer });
await processCodeOutput(baseParams);
@@ -408,5 +415,36 @@ describe('Code Process', () => {
);
});
});
+
+ describe('socket pool isolation', () => {
+ it('should pass dedicated keepAlive:false agents to axios for processCodeOutput', async () => {
+ const smallBuffer = Buffer.alloc(100);
+ mockAxios.mockResolvedValue({ data: smallBuffer });
+
+ await processCodeOutput(baseParams);
+
+ const callConfig = mockAxios.mock.calls[0][0];
+ expect(callConfig.httpAgent).toBe(codeServerHttpAgent);
+ expect(callConfig.httpsAgent).toBe(codeServerHttpsAgent);
+ expect(callConfig.httpAgent).toBeInstanceOf(http.Agent);
+ expect(callConfig.httpsAgent).toBeInstanceOf(https.Agent);
+ expect(callConfig.httpAgent.keepAlive).toBe(false);
+ expect(callConfig.httpsAgent.keepAlive).toBe(false);
+ });
+
+ it('should pass dedicated keepAlive:false agents to axios for getSessionInfo', async () => {
+ mockAxios.mockResolvedValue({
+ data: [{ name: 'sess/fid', lastModified: new Date().toISOString() }],
+ });
+
+ await getSessionInfo('sess/fid', 'api-key');
+
+ const callConfig = mockAxios.mock.calls[0][0];
+ expect(callConfig.httpAgent).toBe(codeServerHttpAgent);
+ expect(callConfig.httpsAgent).toBe(codeServerHttpsAgent);
+ expect(callConfig.httpAgent.keepAlive).toBe(false);
+ expect(callConfig.httpsAgent.keepAlive).toBe(false);
+ });
+ });
});
});
diff --git a/packages/api/src/utils/code.ts b/packages/api/src/utils/code.ts
new file mode 100644
index 0000000000..9ac814a52b
--- /dev/null
+++ b/packages/api/src/utils/code.ts
@@ -0,0 +1,11 @@
+import http from 'http';
+import https from 'https';
+
+/**
+ * Dedicated agents for code-server requests, preventing socket pool contamination.
+ * follow-redirects (used by axios) leaks `socket.destroy` as a timeout listener;
+ * on Node 19+ (keepAlive: true by default), tainted sockets re-enter the global pool
+ * and kill unrelated requests (e.g., node-fetch in CodeExecutor) after the idle timeout.
+ */
+export const codeServerHttpAgent = new http.Agent({ keepAlive: false });
+export const codeServerHttpsAgent = new https.Agent({ keepAlive: false });
diff --git a/packages/api/src/utils/index.ts b/packages/api/src/utils/index.ts
index 5b9315d8c7..a1412e21f2 100644
--- a/packages/api/src/utils/index.ts
+++ b/packages/api/src/utils/index.ts
@@ -1,5 +1,6 @@
export * from './axios';
export * from './azure';
+export * from './code';
export * from './common';
export * from './content';
export * from './email';
From 11ab5f6ee5e5ae955a65bedf00f68a9c2a2d2ecc Mon Sep 17 00:00:00 2001
From: Danny Avila
Date: Thu, 19 Mar 2026 16:42:57 -0400
Subject: [PATCH 09/98] =?UTF-8?q?=F0=9F=9B=82=20fix:=20Reject=20OpenID=20E?=
=?UTF-8?q?mail=20Fallback=20When=20Stored=20`openidId`=20Mismatches=20Tok?=
=?UTF-8?q?en=20Sub=20(#12312)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* 🔐 fix: Reject OpenID email fallback when stored openidId mismatches token sub
When `findOpenIDUser` falls back to email lookup after the primary
`openidId`/`idOnTheSource` query fails, it now rejects any user whose
stored `openidId` differs from the incoming JWT subject claim. This
closes an account-takeover vector where a valid IdP JWT containing a
victim's email but a different `sub` could authenticate as the victim
when OPENID_REUSE_TOKENS is enabled.
The migration path (user has no `openidId` yet) is unaffected.
* test: Validate openidId mismatch guard in email fallback path
Update `findOpenIDUser` unit tests to assert that email-based lookups
returning a user with a different `openidId` are rejected with
AUTH_FAILED. Add matching integration test in `openIdJwtStrategy.spec`
exercising the full verify callback with the real `findOpenIDUser`.
* 🔐 fix: Remove redundant `openidId` truthiness check from mismatch guard
The `&& openidId` middle term in the guard condition caused it to be
bypassed when the incoming token `sub` was empty or undefined. Since
the JS callers can pass `payload?.sub` (which may be undefined), this
created a path where the guard never fired and the email fallback
returned the victim's account. Removing the term ensures the guard
rejects whenever the stored openidId differs from the incoming value,
regardless of whether the incoming value is falsy.
* test: Cover falsy openidId bypass and openidStrategy mismatch rejection
Add regression test for the guard bypass when `openidId` is an empty
string and the email lookup finds a user with a stored openidId.
Add integration test in openidStrategy.spec.js exercising the
mismatch rejection through the full processOpenIDAuth callback,
ensuring both OIDC paths (JWT reuse and standard callback) are
covered.
Restore intent-documenting comment on the no-provider fixture.
---
api/strategies/openIdJwtStrategy.spec.js | 26 +++++++
api/strategies/openidStrategy.spec.js | 27 +++++++
packages/api/src/auth/openid.spec.ts | 95 ++++++++++++++++++------
packages/api/src/auth/openid.ts | 8 +-
4 files changed, 133 insertions(+), 23 deletions(-)
diff --git a/api/strategies/openIdJwtStrategy.spec.js b/api/strategies/openIdJwtStrategy.spec.js
index 79af848046..fd710f1ebd 100644
--- a/api/strategies/openIdJwtStrategy.spec.js
+++ b/api/strategies/openIdJwtStrategy.spec.js
@@ -271,6 +271,32 @@ describe('openIdJwtStrategy – OPENID_EMAIL_CLAIM', () => {
expect(user).toBe(false);
});
+ it('should reject login when email fallback finds user with mismatched openidId', async () => {
+ const emailMatchWithDifferentSub = {
+ _id: 'user-id-2',
+ provider: 'openid',
+ openidId: 'different-sub',
+ email: payload.email,
+ role: SystemRoles.USER,
+ };
+
+ findUser.mockImplementation(async (query) => {
+ if (query.$or) {
+ return null;
+ }
+ if (query.email === payload.email) {
+ return emailMatchWithDifferentSub;
+ }
+ return null;
+ });
+
+ const req = { headers: { authorization: 'Bearer tok' }, session: {} };
+ const { user, info } = await invokeVerify(req, payload);
+
+ expect(user).toBe(false);
+ expect(info).toEqual({ message: 'auth_failed' });
+ });
+
it('should trim whitespace from OPENID_EMAIL_CLAIM', async () => {
process.env.OPENID_EMAIL_CLAIM = ' upn ';
findUser.mockResolvedValue(null);
diff --git a/api/strategies/openidStrategy.spec.js b/api/strategies/openidStrategy.spec.js
index 16fa548a59..4436fab672 100644
--- a/api/strategies/openidStrategy.spec.js
+++ b/api/strategies/openidStrategy.spec.js
@@ -356,6 +356,33 @@ describe('setupOpenId', () => {
expect(updateUser).not.toHaveBeenCalled();
});
+ it('should block login when email fallback finds user with mismatched openidId', async () => {
+ const existingUser = {
+ _id: 'existingUserId',
+ provider: 'openid',
+ openidId: 'different-sub-claim',
+ email: tokenset.claims().email,
+ username: 'existinguser',
+ name: 'Existing User',
+ };
+ findUser.mockImplementation(async (query) => {
+ if (query.$or) {
+ return null;
+ }
+ if (query.email === tokenset.claims().email) {
+ return existingUser;
+ }
+ return null;
+ });
+
+ const result = await validate(tokenset);
+
+ expect(result.user).toBe(false);
+ expect(result.details.message).toBe(ErrorTypes.AUTH_FAILED);
+ expect(createUser).not.toHaveBeenCalled();
+ expect(updateUser).not.toHaveBeenCalled();
+ });
+
it('should enforce the required role and reject login if missing', async () => {
// Arrange – simulate a token without the required role.
jwtDecode.mockReturnValue({
diff --git a/packages/api/src/auth/openid.spec.ts b/packages/api/src/auth/openid.spec.ts
index 7349508ce1..0761a24e85 100644
--- a/packages/api/src/auth/openid.spec.ts
+++ b/packages/api/src/auth/openid.spec.ts
@@ -107,18 +107,18 @@ describe('findOpenIDUser', () => {
});
describe('Email-based searches', () => {
- it('should find user by email when primary conditions fail', async () => {
+ it('should find user by email when primary conditions fail and openidId matches', async () => {
const mockUser: IUser = {
_id: 'user123',
provider: 'openid',
- openidId: 'openid_456',
+ openidId: 'openid_123',
email: 'user@example.com',
username: 'testuser',
} as IUser;
mockFindUser
- .mockResolvedValueOnce(null) // Primary condition fails
- .mockResolvedValueOnce(mockUser); // Email search succeeds
+ .mockResolvedValueOnce(null)
+ .mockResolvedValueOnce(mockUser);
const result = await findOpenIDUser({
openidId: 'openid_123',
@@ -202,7 +202,7 @@ describe('findOpenIDUser', () => {
});
});
- it('should allow login when user has openid provider', async () => {
+ it('should reject email fallback when existing openidId does not match token sub', async () => {
const mockUser: IUser = {
_id: 'user123',
provider: 'openid',
@@ -212,8 +212,34 @@ describe('findOpenIDUser', () => {
} as IUser;
mockFindUser
- .mockResolvedValueOnce(null) // Primary condition fails
- .mockResolvedValueOnce(mockUser); // Email search finds user with openid provider
+ .mockResolvedValueOnce(null)
+ .mockResolvedValueOnce(mockUser);
+
+ const result = await findOpenIDUser({
+ openidId: 'openid_123',
+ findUser: mockFindUser,
+ email: 'user@example.com',
+ });
+
+ expect(result).toEqual({
+ user: null,
+ error: ErrorTypes.AUTH_FAILED,
+ migration: false,
+ });
+ });
+
+ it('should allow email fallback when existing openidId matches token sub', async () => {
+ const mockUser: IUser = {
+ _id: 'user123',
+ provider: 'openid',
+ openidId: 'openid_123',
+ email: 'user@example.com',
+ username: 'testuser',
+ } as IUser;
+
+ mockFindUser
+ .mockResolvedValueOnce(null)
+ .mockResolvedValueOnce(mockUser);
const result = await findOpenIDUser({
openidId: 'openid_123',
@@ -259,7 +285,7 @@ describe('findOpenIDUser', () => {
});
});
- it('should not migrate user who already has openidId', async () => {
+ it('should reject when user already has a different openidId', async () => {
const mockUser: IUser = {
_id: 'user123',
provider: 'openid',
@@ -269,8 +295,8 @@ describe('findOpenIDUser', () => {
} as IUser;
mockFindUser
- .mockResolvedValueOnce(null) // Primary condition fails
- .mockResolvedValueOnce(mockUser); // Email search finds user with existing openidId
+ .mockResolvedValueOnce(null)
+ .mockResolvedValueOnce(mockUser);
const result = await findOpenIDUser({
openidId: 'openid_123',
@@ -279,24 +305,24 @@ describe('findOpenIDUser', () => {
});
expect(result).toEqual({
- user: mockUser,
- error: null,
+ user: null,
+ error: ErrorTypes.AUTH_FAILED,
migration: false,
});
});
- it('should handle user with no provider but existing openidId', async () => {
+ it('should reject when user has no provider but a different openidId', async () => {
const mockUser: IUser = {
_id: 'user123',
openidId: 'existing_openid',
email: 'user@example.com',
username: 'testuser',
- // No provider field
+ // No provider field — tests a different branch than openid-provider mismatch
} as IUser;
mockFindUser
- .mockResolvedValueOnce(null) // Primary condition fails
- .mockResolvedValueOnce(mockUser); // Email search finds user
+ .mockResolvedValueOnce(null)
+ .mockResolvedValueOnce(mockUser);
const result = await findOpenIDUser({
openidId: 'openid_123',
@@ -305,8 +331,8 @@ describe('findOpenIDUser', () => {
});
expect(result).toEqual({
- user: mockUser,
- error: null,
+ user: null,
+ error: ErrorTypes.AUTH_FAILED,
migration: false,
});
});
@@ -398,14 +424,14 @@ describe('findOpenIDUser', () => {
const mockUser: IUser = {
_id: 'user123',
provider: 'openid',
- openidId: 'openid_456',
+ openidId: 'openid_123',
email: 'user@example.com',
username: 'testuser',
} as IUser;
mockFindUser
- .mockResolvedValueOnce(null) // Primary condition fails
- .mockResolvedValueOnce(mockUser); // Email search succeeds
+ .mockResolvedValueOnce(null)
+ .mockResolvedValueOnce(mockUser);
const result = await findOpenIDUser({
openidId: 'openid_123',
@@ -413,7 +439,6 @@ describe('findOpenIDUser', () => {
email: 'User@Example.COM',
});
- /** Email is passed as-is; findUser implementation handles normalization */
expect(mockFindUser).toHaveBeenNthCalledWith(2, { email: 'User@Example.COM' });
expect(result).toEqual({
user: mockUser,
@@ -432,5 +457,31 @@ describe('findOpenIDUser', () => {
}),
).rejects.toThrow('Database error');
});
+
+ it('should reject email fallback when openidId is empty and user has a stored openidId', async () => {
+ const mockUser: IUser = {
+ _id: 'user123',
+ provider: 'openid',
+ openidId: 'existing-real-id',
+ email: 'user@example.com',
+ username: 'testuser',
+ } as IUser;
+
+ mockFindUser.mockResolvedValueOnce(mockUser);
+
+ const result = await findOpenIDUser({
+ openidId: '',
+ findUser: mockFindUser,
+ email: 'user@example.com',
+ });
+
+ expect(mockFindUser).toHaveBeenCalledTimes(1);
+ expect(mockFindUser).toHaveBeenCalledWith({ email: 'user@example.com' });
+ expect(result).toEqual({
+ user: null,
+ error: ErrorTypes.AUTH_FAILED,
+ migration: false,
+ });
+ });
});
});
diff --git a/packages/api/src/auth/openid.ts b/packages/api/src/auth/openid.ts
index a7079ccd16..12ff48b2a9 100644
--- a/packages/api/src/auth/openid.ts
+++ b/packages/api/src/auth/openid.ts
@@ -47,7 +47,13 @@ export async function findOpenIDUser({
return { user: null, error: ErrorTypes.AUTH_FAILED, migration: false };
}
- // If user found by email but doesn't have openidId, prepare for migration
+ if (user?.openidId && user.openidId !== openidId) {
+ logger.warn(
+ `[${strategyName}] Rejected email fallback for ${user.email}: stored openidId does not match token sub`,
+ );
+ return { user: null, error: ErrorTypes.AUTH_FAILED, migration: false };
+ }
+
if (user && !user.openidId) {
logger.info(
`[${strategyName}] Preparing user ${user.email} for migration to OpenID with sub: ${openidId}`,
From 3abad53c16d9b2ada2a5e7c8dfa3cd8fac0b74b7 Mon Sep 17 00:00:00 2001
From: Danny Avila
Date: Thu, 19 Mar 2026 16:44:38 -0400
Subject: [PATCH 10/98] =?UTF-8?q?=F0=9F=93=A6=20chore:=20Bump=20`@dicebear?=
=?UTF-8?q?`=20dependencies=20to=20v9.4.1=20(#12315)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Bump @dicebear/collection and @dicebear/core to version 9.4.1 across multiple package files for consistency and improved functionality.
- Update related dependencies in the client and packages/client directories to ensure compatibility with the new versions.
---
client/package.json | 4 +-
package-lock.json | 307 ++++++++++++++++++-----------------
packages/client/package.json | 4 +-
3 files changed, 164 insertions(+), 151 deletions(-)
diff --git a/client/package.json b/client/package.json
index a3ff5529e5..f42834c1c2 100644
--- a/client/package.json
+++ b/client/package.json
@@ -32,8 +32,8 @@
"@ariakit/react": "^0.4.15",
"@ariakit/react-core": "^0.4.17",
"@codesandbox/sandpack-react": "^2.19.10",
- "@dicebear/collection": "^9.2.2",
- "@dicebear/core": "^9.2.2",
+ "@dicebear/collection": "^9.4.1",
+ "@dicebear/core": "^9.4.1",
"@headlessui/react": "^2.1.2",
"@librechat/client": "*",
"@marsidev/react-turnstile": "^1.1.0",
diff --git a/package-lock.json b/package-lock.json
index a056cc32ef..0b0c0e4888 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -435,8 +435,8 @@
"@ariakit/react": "^0.4.15",
"@ariakit/react-core": "^0.4.17",
"@codesandbox/sandpack-react": "^2.19.10",
- "@dicebear/collection": "^9.2.2",
- "@dicebear/core": "^9.2.2",
+ "@dicebear/collection": "^9.4.1",
+ "@dicebear/core": "^9.4.1",
"@headlessui/react": "^2.1.2",
"@librechat/client": "*",
"@marsidev/react-turnstile": "^1.1.0",
@@ -8538,10 +8538,10 @@
}
},
"node_modules/@dicebear/adventurer": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/adventurer/-/adventurer-9.2.4.tgz",
- "integrity": "sha512-Xvboay3VH1qe7lH17T+bA3qPawf5EjccssDiyhCX/VT0P21c65JyjTIUJV36Nsv08HKeyDscyP0kgt9nPTRKvA==",
- "license": "MIT",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/adventurer/-/adventurer-9.4.1.tgz",
+ "integrity": "sha512-AVEbLK45t6kLnSqcL3AB3Mm3kHhlqpLL6Pa4i9+Jis2O6iwmBZ+x/qmFqV2jQuIxxe55oMRzJLuYGdKWLM8mgg==",
+ "license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
@@ -8550,10 +8550,10 @@
}
},
"node_modules/@dicebear/adventurer-neutral": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/adventurer-neutral/-/adventurer-neutral-9.2.4.tgz",
- "integrity": "sha512-I9IrB4ZYbUHSOUpWoUbfX3vG8FrjcW8htoQ4bEOR7TYOKKE11Mo1nrGMuHZ7GPfwN0CQeK1YVJhWqLTmtYn7Pg==",
- "license": "MIT",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/adventurer-neutral/-/adventurer-neutral-9.4.1.tgz",
+ "integrity": "sha512-5GLdGGpTfwb8Yw5V/nMUim/Re5SgMpDLBpGN/hvlIgobkQt9CnIxTYtSTXmWg+EO8WEAGgMMsj36ts4ELI/CRA==",
+ "license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
@@ -8562,10 +8562,10 @@
}
},
"node_modules/@dicebear/avataaars": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/avataaars/-/avataaars-9.2.4.tgz",
- "integrity": "sha512-QKNBtA/1QGEzR+JjS4XQyrFHYGbzdOp0oa6gjhGhUDrMegDFS8uyjdRfDQsFTebVkyLWjgBQKZEiDqKqHptB6A==",
- "license": "MIT",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/avataaars/-/avataaars-9.4.1.tgz",
+ "integrity": "sha512-qLloK9a7DZoASkjyYWNQpG7TwyIBORJvd5r/h8P0ZRAXvbHRrbpWzM3DT8XEvMU57Dav2i7VC/WdnJwV+72Wng==",
+ "license": "See LICENSE file",
"engines": {
"node": ">=18.0.0"
},
@@ -8574,10 +8574,10 @@
}
},
"node_modules/@dicebear/avataaars-neutral": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/avataaars-neutral/-/avataaars-neutral-9.2.4.tgz",
- "integrity": "sha512-HtBvA7elRv50QTOOsBdtYB1GVimCpGEDlDgWsu1snL5Z3d1+3dIESoXQd3mXVvKTVT8Z9ciA4TEaF09WfxDjAA==",
- "license": "MIT",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/avataaars-neutral/-/avataaars-neutral-9.4.1.tgz",
+ "integrity": "sha512-z5jFq361OKqjXBJnAm3U20+Wducrp3f+Lr2DFDEYFMQXtJ5DiklGcggof3cinueqG8zKFyhcA5oUq06FPUU47A==",
+ "license": "See LICENSE file",
"engines": {
"node": ">=18.0.0"
},
@@ -8586,10 +8586,10 @@
}
},
"node_modules/@dicebear/big-ears": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/big-ears/-/big-ears-9.2.4.tgz",
- "integrity": "sha512-U33tbh7Io6wG6ViUMN5fkWPER7hPKMaPPaYgafaYQlCT4E7QPKF2u8X1XGag3jCKm0uf4SLXfuZ8v+YONcHmNQ==",
- "license": "MIT",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/big-ears/-/big-ears-9.4.1.tgz",
+ "integrity": "sha512-30P4Q3n0pCgfFwVgiFTm+dQiJUmF+j8I71nQM+dUIGynrzkGq1vGSdhyTWfr8g/X7wPgsHhW1GEro71o0d1wvA==",
+ "license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
@@ -8598,10 +8598,10 @@
}
},
"node_modules/@dicebear/big-ears-neutral": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/big-ears-neutral/-/big-ears-neutral-9.2.4.tgz",
- "integrity": "sha512-pPjYu80zMFl43A9sa5+tAKPkhp4n9nd7eN878IOrA1HAowh/XePh5JN8PTkNFS9eM+rnN9m8WX08XYFe30kLYw==",
- "license": "MIT",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/big-ears-neutral/-/big-ears-neutral-9.4.1.tgz",
+ "integrity": "sha512-VsDZoTRWsXMeXRSF5eDD+WQDt7gXZ0nssg0GOELkk8kQ+AWe5rtzyDarRPCBO6t63kVaaslcE7er30F/k9m1wg==",
+ "license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
@@ -8610,10 +8610,10 @@
}
},
"node_modules/@dicebear/big-smile": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/big-smile/-/big-smile-9.2.4.tgz",
- "integrity": "sha512-zeEfXOOXy7j9tfkPLzfQdLBPyQsctBetTdEfKRArc1k3RUliNPxfJG9j88+cXQC6GXrVW2pcT2X50NSPtugCFQ==",
- "license": "MIT",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/big-smile/-/big-smile-9.4.1.tgz",
+ "integrity": "sha512-II+/4AIuf6StMAXz8xGjenHRfYkwuJlZM0dFGGxHHQR4Cr5h4+lJ74BeH0vxpCbCe0LZpPXbkxZ5qFB0IEXltQ==",
+ "license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
@@ -8622,10 +8622,10 @@
}
},
"node_modules/@dicebear/bottts": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/bottts/-/bottts-9.2.4.tgz",
- "integrity": "sha512-4CTqrnVg+NQm6lZ4UuCJish8gGWe8EqSJrzvHQRO5TEyAKjYxbTdVqejpkycG1xkawha4FfxsYgtlSx7UwoVMw==",
- "license": "MIT",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/bottts/-/bottts-9.4.1.tgz",
+ "integrity": "sha512-VgzXdRN+685i8MJ16xfw7ly6jKWqUkDnTv61cb6kkvSLfUTmJopYU0K8YWGAlURWAJux/IYA7EDh6SQ6AnACxQ==",
+ "license": "See LICENSE file",
"engines": {
"node": ">=18.0.0"
},
@@ -8634,10 +8634,10 @@
}
},
"node_modules/@dicebear/bottts-neutral": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/bottts-neutral/-/bottts-neutral-9.2.4.tgz",
- "integrity": "sha512-eMVdofdD/udHsKIaeWEXShDRtiwk7vp4FjY7l0f79vIzfhkIsXKEhPcnvHKOl/yoArlDVS3Uhgjj0crWTO9RJA==",
- "license": "MIT",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/bottts-neutral/-/bottts-neutral-9.4.1.tgz",
+ "integrity": "sha512-53wdnsvi9RjOmaOo3tA5bUZU0azUVQaF90JjhABmmX1mwJ2lHJXh+Np6X/PmTkZ+2zLVjnhQKZ0KRfX/Um+S+g==",
+ "license": "See LICENSE file",
"engines": {
"node": ">=18.0.0"
},
@@ -8646,41 +8646,42 @@
}
},
"node_modules/@dicebear/collection": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/collection/-/collection-9.2.4.tgz",
- "integrity": "sha512-I1wCUp0yu5qSIeMQHmDYXQIXKkKjcja/SYBxppPkYFXpR2alxb0k9/swFDdMbkY6a1c9AT1kI1y+Pg6ywQ2rTA==",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/collection/-/collection-9.4.1.tgz",
+ "integrity": "sha512-sgu4JGrpyJmxB+LdUvSy0iYfdlTRbuyaKozS62Q8+FYWKIVkrcKpeJjD+6kwDKaBozAsa/8zgH3RhhxfkhROcA==",
"license": "MIT",
"dependencies": {
- "@dicebear/adventurer": "9.2.4",
- "@dicebear/adventurer-neutral": "9.2.4",
- "@dicebear/avataaars": "9.2.4",
- "@dicebear/avataaars-neutral": "9.2.4",
- "@dicebear/big-ears": "9.2.4",
- "@dicebear/big-ears-neutral": "9.2.4",
- "@dicebear/big-smile": "9.2.4",
- "@dicebear/bottts": "9.2.4",
- "@dicebear/bottts-neutral": "9.2.4",
- "@dicebear/croodles": "9.2.4",
- "@dicebear/croodles-neutral": "9.2.4",
- "@dicebear/dylan": "9.2.4",
- "@dicebear/fun-emoji": "9.2.4",
- "@dicebear/glass": "9.2.4",
- "@dicebear/icons": "9.2.4",
- "@dicebear/identicon": "9.2.4",
- "@dicebear/initials": "9.2.4",
- "@dicebear/lorelei": "9.2.4",
- "@dicebear/lorelei-neutral": "9.2.4",
- "@dicebear/micah": "9.2.4",
- "@dicebear/miniavs": "9.2.4",
- "@dicebear/notionists": "9.2.4",
- "@dicebear/notionists-neutral": "9.2.4",
- "@dicebear/open-peeps": "9.2.4",
- "@dicebear/personas": "9.2.4",
- "@dicebear/pixel-art": "9.2.4",
- "@dicebear/pixel-art-neutral": "9.2.4",
- "@dicebear/rings": "9.2.4",
- "@dicebear/shapes": "9.2.4",
- "@dicebear/thumbs": "9.2.4"
+ "@dicebear/adventurer": "9.4.1",
+ "@dicebear/adventurer-neutral": "9.4.1",
+ "@dicebear/avataaars": "9.4.1",
+ "@dicebear/avataaars-neutral": "9.4.1",
+ "@dicebear/big-ears": "9.4.1",
+ "@dicebear/big-ears-neutral": "9.4.1",
+ "@dicebear/big-smile": "9.4.1",
+ "@dicebear/bottts": "9.4.1",
+ "@dicebear/bottts-neutral": "9.4.1",
+ "@dicebear/croodles": "9.4.1",
+ "@dicebear/croodles-neutral": "9.4.1",
+ "@dicebear/dylan": "9.4.1",
+ "@dicebear/fun-emoji": "9.4.1",
+ "@dicebear/glass": "9.4.1",
+ "@dicebear/icons": "9.4.1",
+ "@dicebear/identicon": "9.4.1",
+ "@dicebear/initials": "9.4.1",
+ "@dicebear/lorelei": "9.4.1",
+ "@dicebear/lorelei-neutral": "9.4.1",
+ "@dicebear/micah": "9.4.1",
+ "@dicebear/miniavs": "9.4.1",
+ "@dicebear/notionists": "9.4.1",
+ "@dicebear/notionists-neutral": "9.4.1",
+ "@dicebear/open-peeps": "9.4.1",
+ "@dicebear/personas": "9.4.1",
+ "@dicebear/pixel-art": "9.4.1",
+ "@dicebear/pixel-art-neutral": "9.4.1",
+ "@dicebear/rings": "9.4.1",
+ "@dicebear/shapes": "9.4.1",
+ "@dicebear/thumbs": "9.4.1",
+ "@dicebear/toon-head": "9.4.1"
},
"engines": {
"node": ">=18.0.0"
@@ -8690,22 +8691,22 @@
}
},
"node_modules/@dicebear/core": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/core/-/core-9.2.4.tgz",
- "integrity": "sha512-hz6zArEcUwkZzGOSJkWICrvqnEZY7BKeiq9rqKzVJIc1tRVv0MkR0FGvIxSvXiK9TTIgKwu656xCWAGAl6oh+w==",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/core/-/core-9.4.1.tgz",
+ "integrity": "sha512-yzmoEhAc6CTaY9v0xz4MI3FTt5I5O+cvphpE+kd6Qz8XjW3/YveXEQcdO4CW0CTSUao88a8+/IMvnMGfUmDW1Q==",
"license": "MIT",
"dependencies": {
- "@types/json-schema": "^7.0.11"
+ "@types/json-schema": "^7.0.15"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@dicebear/croodles": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/croodles/-/croodles-9.2.4.tgz",
- "integrity": "sha512-CqT0NgVfm+5kd+VnjGY4WECNFeOrj5p7GCPTSEA7tCuN72dMQOX47P9KioD3wbExXYrIlJgOcxNrQeb/FMGc3A==",
- "license": "MIT",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/croodles/-/croodles-9.4.1.tgz",
+ "integrity": "sha512-N1LQRi45JUIawKRMTYDuUbQMxUwcGUULcdUkGy1oCgTwnjnbFhZ2+hIsSTghyrjE44U8N3Rc8msTOzIVkioiYA==",
+ "license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
@@ -8714,10 +8715,10 @@
}
},
"node_modules/@dicebear/croodles-neutral": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/croodles-neutral/-/croodles-neutral-9.2.4.tgz",
- "integrity": "sha512-8vAS9lIEKffSUVx256GSRAlisB8oMX38UcPWw72venO/nitLVsyZ6hZ3V7eBdII0Onrjqw1RDndslQODbVcpTw==",
- "license": "MIT",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/croodles-neutral/-/croodles-neutral-9.4.1.tgz",
+ "integrity": "sha512-FkA29zAvWKZF8DYIIBGedsqNpIUmj4/NOxcEHgxhWH4AxW6zwv0gZ29lbQ0tUlDah7yQfbV7beLepwr2pVYYZQ==",
+ "license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
@@ -8726,10 +8727,10 @@
}
},
"node_modules/@dicebear/dylan": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/dylan/-/dylan-9.2.4.tgz",
- "integrity": "sha512-tiih1358djAq0jDDzmW3N3S4C3ynC2yn4hhlTAq/MaUAQtAi47QxdHdFGdxH0HBMZKqA4ThLdVk3yVgN4xsukg==",
- "license": "MIT",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/dylan/-/dylan-9.4.1.tgz",
+ "integrity": "sha512-CX2lEJ3nXjWnp18VIDM++be36qFVz8yUpNvf7K5Ehh//tmfGZ/GjdTWpXk79baNM5JgUEnzCMHpAM0IU3jt8rQ==",
+ "license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
@@ -8738,10 +8739,10 @@
}
},
"node_modules/@dicebear/fun-emoji": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/fun-emoji/-/fun-emoji-9.2.4.tgz",
- "integrity": "sha512-Od729skczse1HvHekgEFv+mSuJKMC4sl5hENGi/izYNe6DZDqJrrD0trkGT/IVh/SLXUFbq1ZFY9I2LoUGzFZg==",
- "license": "MIT",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/fun-emoji/-/fun-emoji-9.4.1.tgz",
+ "integrity": "sha512-ys9Q/wCZt48bFlGbHQPLrwGnOhe40fPxoHH6n9NLKoWXEEoXE7wExQje40FGDFOSk7Ja+PvvYDkTd365AMRGEA==",
+ "license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
@@ -8750,9 +8751,9 @@
}
},
"node_modules/@dicebear/glass": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/glass/-/glass-9.2.4.tgz",
- "integrity": "sha512-5lxbJode1t99eoIIgW0iwZMoZU4jNMJv/6vbsgYUhAslYFX5zP0jVRscksFuo89TTtS7YKqRqZAL3eNhz4bTDw==",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/glass/-/glass-9.4.1.tgz",
+ "integrity": "sha512-W5zFrlZxa0UHDKUWwAVdZA6H42fOZ1uQC4mlt4dPOV7ZAYmx1ZcfvHYGeNuY87fJaeD9RZd+FnIGKRKAZdwf+g==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
@@ -8762,9 +8763,9 @@
}
},
"node_modules/@dicebear/icons": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/icons/-/icons-9.2.4.tgz",
- "integrity": "sha512-bRsK1qj8u9Z76xs8XhXlgVr/oHh68tsHTJ/1xtkX9DeTQTSamo2tS26+r231IHu+oW3mePtFnwzdG9LqEPRd4A==",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/icons/-/icons-9.4.1.tgz",
+ "integrity": "sha512-O7LIY8ksjAvWfW9o4ImFbzd8kFEXJet7LRMTK9RXU6ch9WZwiqNlrAB0oVkKI1aRAH2CM7wSfuTUjQxQF3BTVA==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
@@ -8774,9 +8775,9 @@
}
},
"node_modules/@dicebear/identicon": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/identicon/-/identicon-9.2.4.tgz",
- "integrity": "sha512-R9nw/E8fbu9HltHOqI9iL/o9i7zM+2QauXWMreQyERc39oGR9qXiwgBxsfYGcIS4C85xPyuL5B3I2RXrLBlJPg==",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/identicon/-/identicon-9.4.1.tgz",
+ "integrity": "sha512-xzIh8znm/OGAq6WHIkapn3pvjC+oEp7b9nyWSwuFmt5ESVKVO+ppD3TCDOe9Q5ZFXdL9ZijmdCR28Hvi5Ku/PA==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
@@ -8786,9 +8787,9 @@
}
},
"node_modules/@dicebear/initials": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/initials/-/initials-9.2.4.tgz",
- "integrity": "sha512-4SzHG5WoQZl1TGcpEZR4bdsSkUVqwNQCOwWSPAoBJa3BNxbVsvL08LF7I97BMgrCoknWZjQHUYt05amwTPTKtg==",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/initials/-/initials-9.4.1.tgz",
+ "integrity": "sha512-DnTK2Du3CIVCSqER80VgfMl47sa6eIHyq1kSkd9Y9D+ClfD8WDxpyHw8iOVGQ1nro7lIr7SfBb9P1aTNK0+FuA==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
@@ -8798,9 +8799,9 @@
}
},
"node_modules/@dicebear/lorelei": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/lorelei/-/lorelei-9.2.4.tgz",
- "integrity": "sha512-eS4mPYUgDpo89HvyFAx/kgqSSKh8W4zlUA8QJeIUCWTB0WpQmeqkSgIyUJjGDYSrIujWi+zEhhckksM5EwW0Dg==",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/lorelei/-/lorelei-9.4.1.tgz",
+ "integrity": "sha512-bB1N8yFdumo3G/3N91L4mismx50PQx4Gu8JwhN2UDH+aHpZ222TAJNr4xe5d6lrIB06VzlNhgLBANoUKRW6Wcg==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
@@ -8810,9 +8811,9 @@
}
},
"node_modules/@dicebear/lorelei-neutral": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/lorelei-neutral/-/lorelei-neutral-9.2.4.tgz",
- "integrity": "sha512-bWq2/GonbcJULtT+B/MGcM2UnA7kBQoH+INw8/oW83WI3GNTZ6qEwe3/W4QnCgtSOhUsuwuiSULguAFyvtkOZQ==",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/lorelei-neutral/-/lorelei-neutral-9.4.1.tgz",
+ "integrity": "sha512-VunhzhsNmccxNiaSvQ8pL4/ZluMHJ1H7B69irwLJm+SqjyoWrjVUg0FFoJ8ZEkx0j6LSlPKr9nRxXy02Sk73Ng==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
@@ -8822,10 +8823,10 @@
}
},
"node_modules/@dicebear/micah": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/micah/-/micah-9.2.4.tgz",
- "integrity": "sha512-XNWJ8Mx+pncIV8Ye0XYc/VkMiax8kTxcP3hLTC5vmELQyMSLXzg/9SdpI+W/tCQghtPZRYTT3JdY9oU9IUlP2g==",
- "license": "MIT",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/micah/-/micah-9.4.1.tgz",
+ "integrity": "sha512-7yatVxu1k6NKNe4SeJwr1j8hBEZT2Eftmk1LPL8OOMwFNfm4/tY9ZQMf9T5bOBkOGIDH0mvY8rw7kq99PgPNGg==",
+ "license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
@@ -8834,10 +8835,10 @@
}
},
"node_modules/@dicebear/miniavs": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/miniavs/-/miniavs-9.2.4.tgz",
- "integrity": "sha512-k7IYTAHE/4jSO6boMBRrNlqPT3bh7PLFM1atfe0nOeCDwmz/qJUBP3HdONajbf3fmo8f2IZYhELrNWTOE7Ox3Q==",
- "license": "MIT",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/miniavs/-/miniavs-9.4.1.tgz",
+ "integrity": "sha512-35/koev3bsDPchre9xjCY+QgyKUTl+TdyuBp0Ve2ixbE/Ywe5BSGwK4Uixon7ZA4+XEivpF9KNEn+9FSQLGHCQ==",
+ "license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
@@ -8846,9 +8847,9 @@
}
},
"node_modules/@dicebear/notionists": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/notionists/-/notionists-9.2.4.tgz",
- "integrity": "sha512-zcvpAJ93EfC0xQffaPZQuJPShwPhnu9aTcoPsaYGmw0oEDLcv2XYmDhUUdX84QYCn6LtCZH053rHLVazRW+OGw==",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/notionists/-/notionists-9.4.1.tgz",
+ "integrity": "sha512-AQLwB1nyePPHF2voI+f6u/Rqt5az+deMdxG9XeVTNrGc57L5dkD+hSAryELcp2Zjn45kL/3CX2aZb2usdXtdQA==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
@@ -8858,9 +8859,9 @@
}
},
"node_modules/@dicebear/notionists-neutral": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/notionists-neutral/-/notionists-neutral-9.2.4.tgz",
- "integrity": "sha512-fskWzBVxQzJhCKqY24DGZbYHSBaauoRa1DgXM7+7xBuksH7mfbTmZTvnUAsAqJYBkla8IPb4ERKduDWtlWYYjQ==",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/notionists-neutral/-/notionists-neutral-9.4.1.tgz",
+ "integrity": "sha512-sCgf3T08az1mFQj6mlrgIh5pFmiBbqBhJXVA75uCd56g9UiXH8BG1eraIqYYMRpHX6JF2FHC8EGcmtqPNnl9Bg==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
@@ -8870,9 +8871,9 @@
}
},
"node_modules/@dicebear/open-peeps": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/open-peeps/-/open-peeps-9.2.4.tgz",
- "integrity": "sha512-s6nwdjXFsplqEI7imlsel4Gt6kFVJm6YIgtZSpry0UdwDoxUUudei5bn957j9lXwVpVUcRjJW+TuEKztYjXkKQ==",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/open-peeps/-/open-peeps-9.4.1.tgz",
+ "integrity": "sha512-pdtttjRm55PNBk43K4nIXy07VWWcX7Ds4iAk0VyPhYkXN6ndDDMtrkxVxSeroO1LswMD58MTfc0D9Iv7iyI9SA==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
@@ -8882,10 +8883,10 @@
}
},
"node_modules/@dicebear/personas": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/personas/-/personas-9.2.4.tgz",
- "integrity": "sha512-JNim8RfZYwb0MfxW6DLVfvreCFIevQg+V225Xe5tDfbFgbcYEp4OU/KaiqqO2476OBjCw7i7/8USbv2acBhjwA==",
- "license": "MIT",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/personas/-/personas-9.4.1.tgz",
+ "integrity": "sha512-3gVfj3ST/kDhg1GBd67rAiWUpg3+ZFm4lsxu6m6bjgEHff6dR6Mu3zl2sjz/wc+iqJjO30EFIkHgJU06Dwah2g==",
+ "license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
@@ -8894,9 +8895,9 @@
}
},
"node_modules/@dicebear/pixel-art": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/pixel-art/-/pixel-art-9.2.4.tgz",
- "integrity": "sha512-4Ao45asieswUdlCTBZqcoF/0zHR3OWUWB0Mvhlu9b1Fbc6IlPBiOfx2vsp6bnVGVnMag58tJLecx2omeXdECBQ==",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/pixel-art/-/pixel-art-9.4.1.tgz",
+ "integrity": "sha512-0CfWusT9nZp/s4bBuqJWhDLFtIGVGANDq5+X8R58wvXC0AscyxJfpVgz5v0rNacr55JbiQIqHLlU/ZpxDSBeqg==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
@@ -8906,9 +8907,9 @@
}
},
"node_modules/@dicebear/pixel-art-neutral": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/pixel-art-neutral/-/pixel-art-neutral-9.2.4.tgz",
- "integrity": "sha512-ZITPLD1cPN4GjKkhWi80s7e5dcbXy34ijWlvmxbc4eb/V7fZSsyRa9EDUW3QStpo+xrCJLcLR+3RBE5iz0PC/A==",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/pixel-art-neutral/-/pixel-art-neutral-9.4.1.tgz",
+ "integrity": "sha512-DFSCVZCUA6IGs+jcgxPbTdbWbjOD/YbAn1cvipSaO1zSRNl6xkgYJyCBkqPAiiKgQ1Fc1DhkrRUEXF/2YeC5LA==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
@@ -8918,9 +8919,9 @@
}
},
"node_modules/@dicebear/rings": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/rings/-/rings-9.2.4.tgz",
- "integrity": "sha512-teZxELYyV2ogzgb5Mvtn/rHptT0HXo9SjUGS4A52mOwhIdHSGGU71MqA1YUzfae9yJThsw6K7Z9kzuY2LlZZHA==",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/rings/-/rings-9.4.1.tgz",
+ "integrity": "sha512-L+rJt94B2a4xT8dyaz7TEqkSKEvvNGO1uTNGSUaM5YPM9DU9dNdnE30VC+MoXVE6sfcMSGKbxWsbzwEx49Y8Kg==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
@@ -8930,9 +8931,9 @@
}
},
"node_modules/@dicebear/shapes": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/shapes/-/shapes-9.2.4.tgz",
- "integrity": "sha512-MhK9ZdFm1wUnH4zWeKPRMZ98UyApolf5OLzhCywfu38tRN6RVbwtBRHc/42ZwoN1JU1JgXr7hzjYucMqISHtbA==",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/shapes/-/shapes-9.4.1.tgz",
+ "integrity": "sha512-uBomOUBNhhVyEvGcAyo+Fj939ZYdpWnvz95/71Kkh3FdTB9ZNeLongz0jmy1t/UJyJooTgIQFYTks/08FDuEJg==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
@@ -8942,9 +8943,9 @@
}
},
"node_modules/@dicebear/thumbs": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/@dicebear/thumbs/-/thumbs-9.2.4.tgz",
- "integrity": "sha512-EL4sMqv9p2+1Xy3d8e8UxyeKZV2+cgt3X2x2RTRzEOIIhobtkL8u6lJxmJbiGbpVtVALmrt5e7gjmwqpryYDpg==",
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/thumbs/-/thumbs-9.4.1.tgz",
+ "integrity": "sha512-vJsXw7qoCeFht/RFE3NK9mKxhWv91vE232Au33Ypq1DRg8gLtoC+3RMTHj5mfQVhrObGgv/HTCTOrQw+9l4phg==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
@@ -8953,6 +8954,18 @@
"@dicebear/core": "^9.0.0"
}
},
+ "node_modules/@dicebear/toon-head": {
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@dicebear/toon-head/-/toon-head-9.4.1.tgz",
+ "integrity": "sha512-cBpH4p5cH+CWu4cPvTgqWZLyTqrTQq9/hN22JvHyqugehzCYic2B2a1+mUstixIv5LDqPYP/4pEzbE3cjqmjYw==",
+ "license": "(MIT AND CC-BY-4.0)",
+ "engines": {
+ "node": ">=16.0.0"
+ },
+ "peerDependencies": {
+ "@dicebear/core": "^9.0.0"
+ }
+ },
"node_modules/@emnapi/core": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz",
@@ -43947,8 +43960,8 @@
"peerDependencies": {
"@ariakit/react": "^0.4.16",
"@ariakit/react-core": "^0.4.17",
- "@dicebear/collection": "^9.2.2",
- "@dicebear/core": "^9.2.2",
+ "@dicebear/collection": "^9.4.1",
+ "@dicebear/core": "^9.4.1",
"@headlessui/react": "^2.1.2",
"@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-alert-dialog": "1.0.2",
diff --git a/packages/client/package.json b/packages/client/package.json
index e76c1d075a..908f9f98f7 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -31,8 +31,8 @@
"peerDependencies": {
"@ariakit/react": "^0.4.16",
"@ariakit/react-core": "^0.4.17",
- "@dicebear/collection": "^9.2.2",
- "@dicebear/core": "^9.2.2",
+ "@dicebear/collection": "^9.4.1",
+ "@dicebear/core": "^9.4.1",
"@headlessui/react": "^2.1.2",
"@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-alert-dialog": "1.0.2",
From ec0238d7cad8dbeab27d56be58d43b2059e9e8d9 Mon Sep 17 00:00:00 2001
From: Danny Avila
Date: Thu, 19 Mar 2026 17:07:08 -0400
Subject: [PATCH 11/98] =?UTF-8?q?=F0=9F=90=B3=20chore:=20Upgrade=20Alpine?=
=?UTF-8?q?=20packages=20in=20Dockerfiles=20(#12316)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Added `apk upgrade --no-cache` to both Dockerfile and Dockerfile.multi to ensure all installed packages are up to date.
- Maintained the installation of `jemalloc` and other dependencies without changes.
---
Dockerfile | 2 +-
Dockerfile.multi | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index 02bda8a589..3c4963f970 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,7 +3,7 @@
# Base node image
FROM node:20-alpine AS node
-# Install jemalloc
+RUN apk upgrade --no-cache
RUN apk add --no-cache jemalloc
RUN apk add --no-cache python3 py3-pip uv
diff --git a/Dockerfile.multi b/Dockerfile.multi
index 8e7483e378..bc4203f265 100644
--- a/Dockerfile.multi
+++ b/Dockerfile.multi
@@ -6,7 +6,7 @@ ARG NODE_MAX_OLD_SPACE_SIZE=6144
# Base for all builds
FROM node:20-alpine AS base-min
-# Install jemalloc
+RUN apk upgrade --no-cache
RUN apk add --no-cache jemalloc
# Set environment variable to use jemalloc
ENV LD_PRELOAD=/usr/lib/libjemalloc.so.2
From f380390408ffe0ae1470d4c5217224e7de656947 Mon Sep 17 00:00:00 2001
From: Danny Avila
Date: Thu, 19 Mar 2026 17:15:12 -0400
Subject: [PATCH 12/98] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20fix:=20Prevent=20?=
=?UTF-8?q?loop=20in=20ChatGPT=20import=20on=20Cyclic=20Parent=20Graphs=20?=
=?UTF-8?q?(#12313)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Cap adjustTimestampsForOrdering to N passes and add cycle detection
to findValidParent, preventing DoS via crafted ChatGPT export files
with cyclic parentMessageId relationships.
Add breakParentCycles to sever cyclic back-edges before saving,
ensuring structurally valid message trees are persisted to the DB.
---
.../utils/import/importers-timestamp.spec.js | 128 ++++++++++++++++++
api/server/utils/import/importers.js | 117 ++++++++++++----
2 files changed, 219 insertions(+), 26 deletions(-)
diff --git a/api/server/utils/import/importers-timestamp.spec.js b/api/server/utils/import/importers-timestamp.spec.js
index c7665dfe25..02f24f72ae 100644
--- a/api/server/utils/import/importers-timestamp.spec.js
+++ b/api/server/utils/import/importers-timestamp.spec.js
@@ -1,3 +1,4 @@
+const { logger } = require('@librechat/data-schemas');
const { Constants } = require('librechat-data-provider');
const { ImportBatchBuilder } = require('./importBatchBuilder');
const { getImporter } = require('./importers');
@@ -368,6 +369,133 @@ describe('Import Timestamp Ordering', () => {
new Date(nullTimeMsg.createdAt).getTime(),
);
});
+
+ test('should terminate on cyclic parent relationships and break cycles before saving', async () => {
+ const warnSpy = jest.spyOn(logger, 'warn');
+ const jsonData = [
+ {
+ title: 'Cycle Test',
+ create_time: 1700000000,
+ mapping: {
+ 'root-node': {
+ id: 'root-node',
+ message: null,
+ parent: null,
+ children: ['message-a'],
+ },
+ 'message-a': {
+ id: 'message-a',
+ message: {
+ id: 'message-a',
+ author: { role: 'user' },
+ create_time: 1700000000,
+ content: { content_type: 'text', parts: ['Message A'] },
+ metadata: {},
+ },
+ parent: 'message-b',
+ children: ['message-b'],
+ },
+ 'message-b': {
+ id: 'message-b',
+ message: {
+ id: 'message-b',
+ author: { role: 'assistant' },
+ create_time: 1700000000,
+ content: { content_type: 'text', parts: ['Message B'] },
+ metadata: {},
+ },
+ parent: 'message-a',
+ children: ['message-a'],
+ },
+ },
+ },
+ ];
+
+ const requestUserId = 'user-123';
+ const importBatchBuilder = new ImportBatchBuilder(requestUserId);
+
+ const importer = getImporter(jsonData);
+ await importer(jsonData, requestUserId, () => importBatchBuilder);
+
+ const { messages } = importBatchBuilder;
+ expect(messages).toHaveLength(2);
+
+ const msgA = messages.find((m) => m.text === 'Message A');
+ const msgB = messages.find((m) => m.text === 'Message B');
+ expect(msgA).toBeDefined();
+ expect(msgB).toBeDefined();
+
+ const roots = messages.filter((m) => m.parentMessageId === Constants.NO_PARENT);
+ expect(roots).toHaveLength(1);
+
+ const [root] = roots;
+ const nonRoot = messages.find((m) => m.parentMessageId !== Constants.NO_PARENT);
+ expect(nonRoot.parentMessageId).toBe(root.messageId);
+
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('cyclic parent relationships'));
+ warnSpy.mockRestore();
+ });
+
+ test('should not hang when findValidParent encounters a skippable-message cycle', async () => {
+ const jsonData = [
+ {
+ title: 'Skippable Cycle Test',
+ create_time: 1700000000,
+ mapping: {
+ 'root-node': {
+ id: 'root-node',
+ message: null,
+ parent: null,
+ children: ['real-msg'],
+ },
+ 'sys-a': {
+ id: 'sys-a',
+ message: {
+ id: 'sys-a',
+ author: { role: 'system' },
+ create_time: 1700000000,
+ content: { content_type: 'text', parts: ['system a'] },
+ metadata: {},
+ },
+ parent: 'sys-b',
+ children: ['real-msg'],
+ },
+ 'sys-b': {
+ id: 'sys-b',
+ message: {
+ id: 'sys-b',
+ author: { role: 'system' },
+ create_time: 1700000000,
+ content: { content_type: 'text', parts: ['system b'] },
+ metadata: {},
+ },
+ parent: 'sys-a',
+ children: [],
+ },
+ 'real-msg': {
+ id: 'real-msg',
+ message: {
+ id: 'real-msg',
+ author: { role: 'user' },
+ create_time: 1700000001,
+ content: { content_type: 'text', parts: ['Hello'] },
+ metadata: {},
+ },
+ parent: 'sys-a',
+ children: [],
+ },
+ },
+ },
+ ];
+
+ const importBatchBuilder = new ImportBatchBuilder('user-123');
+ const importer = getImporter(jsonData);
+ await importer(jsonData, 'user-123', () => importBatchBuilder);
+
+ const realMsg = importBatchBuilder.messages.find((m) => m.text === 'Hello');
+ expect(realMsg).toBeDefined();
+ expect(realMsg.parentMessageId).toBe(Constants.NO_PARENT);
+ });
});
describe('Comparison with Fork Functionality', () => {
diff --git a/api/server/utils/import/importers.js b/api/server/utils/import/importers.js
index 81a0f048df..39734c181c 100644
--- a/api/server/utils/import/importers.js
+++ b/api/server/utils/import/importers.js
@@ -324,32 +324,42 @@ function processConversation(conv, importBatchBuilder, requestUserId) {
}
/**
- * Helper function to find the nearest valid parent (skips system, reasoning_recap, and thoughts messages)
- * @param {string} parentId - The ID of the parent message.
+ * Finds the nearest valid parent by traversing up through skippable messages
+ * (system, reasoning_recap, thoughts). Uses iterative traversal to avoid
+ * stack overflow on deep chains of skippable messages.
+ *
+ * @param {string} startId - The ID of the starting parent message.
* @returns {string} The ID of the nearest valid parent message.
*/
- const findValidParent = (parentId) => {
- if (!parentId || !messageMap.has(parentId)) {
- return Constants.NO_PARENT;
+ const findValidParent = (startId) => {
+ const visited = new Set();
+ let parentId = startId;
+
+ while (parentId) {
+ if (!messageMap.has(parentId) || visited.has(parentId)) {
+ return Constants.NO_PARENT;
+ }
+ visited.add(parentId);
+
+ const parentMapping = conv.mapping[parentId];
+ if (!parentMapping?.message) {
+ return Constants.NO_PARENT;
+ }
+
+ const contentType = parentMapping.message.content?.content_type;
+ const shouldSkip =
+ parentMapping.message.author?.role === 'system' ||
+ contentType === 'reasoning_recap' ||
+ contentType === 'thoughts';
+
+ if (!shouldSkip) {
+ return messageMap.get(parentId);
+ }
+
+ parentId = parentMapping.parent;
}
- const parentMapping = conv.mapping[parentId];
- if (!parentMapping?.message) {
- return Constants.NO_PARENT;
- }
-
- /* If parent is a system message, reasoning_recap, or thoughts, traverse up to find the nearest valid parent */
- const contentType = parentMapping.message.content?.content_type;
- const shouldSkip =
- parentMapping.message.author?.role === 'system' ||
- contentType === 'reasoning_recap' ||
- contentType === 'thoughts';
-
- if (shouldSkip) {
- return findValidParent(parentMapping.parent);
- }
-
- return messageMap.get(parentId);
+ return Constants.NO_PARENT;
};
/**
@@ -466,7 +476,10 @@ function processConversation(conv, importBatchBuilder, requestUserId) {
messages.push(message);
}
- adjustTimestampsForOrdering(messages);
+ const cycleDetected = adjustTimestampsForOrdering(messages);
+ if (cycleDetected) {
+ breakParentCycles(messages);
+ }
for (const message of messages) {
importBatchBuilder.saveMessage(message);
@@ -553,21 +566,30 @@ function formatMessageText(messageData) {
* Messages are sorted by createdAt and buildTree expects parents to appear before children.
* ChatGPT exports can have slight timestamp inversions (e.g., tool call results
* arriving a few ms before their parent). Uses multiple passes to handle cascading adjustments.
+ * Capped at N passes (where N = message count) to guarantee termination on cyclic graphs.
*
* @param {Array} messages - Array of message objects with messageId, parentMessageId, and createdAt.
+ * @returns {boolean} True if cyclic parent relationships were detected.
*/
function adjustTimestampsForOrdering(messages) {
+ if (messages.length === 0) {
+ return false;
+ }
+
const timestampMap = new Map();
- messages.forEach((msg) => timestampMap.set(msg.messageId, msg.createdAt));
+ for (const msg of messages) {
+ timestampMap.set(msg.messageId, msg.createdAt);
+ }
let hasChanges = true;
- while (hasChanges) {
+ let remainingPasses = messages.length;
+ while (hasChanges && remainingPasses > 0) {
hasChanges = false;
+ remainingPasses--;
for (const message of messages) {
if (message.parentMessageId && message.parentMessageId !== Constants.NO_PARENT) {
const parentTimestamp = timestampMap.get(message.parentMessageId);
if (parentTimestamp && message.createdAt <= parentTimestamp) {
- // Bump child timestamp to 1ms after parent
message.createdAt = new Date(parentTimestamp.getTime() + 1);
timestampMap.set(message.messageId, message.createdAt);
hasChanges = true;
@@ -575,6 +597,49 @@ function adjustTimestampsForOrdering(messages) {
}
}
}
+
+ const cycleDetected = remainingPasses === 0 && hasChanges;
+ if (cycleDetected) {
+ logger.warn(
+ '[importers] Detected cyclic parent relationships while adjusting import timestamps',
+ );
+ }
+ return cycleDetected;
+}
+
+/**
+ * Severs cyclic parentMessageId back-edges so saved messages form a valid tree.
+ * Walks each message's parent chain; if a message is visited twice, its parentMessageId
+ * is set to NO_PARENT to break the cycle.
+ *
+ * @param {Array} messages - Array of message objects with messageId and parentMessageId.
+ */
+function breakParentCycles(messages) {
+ const parentLookup = new Map();
+ for (const msg of messages) {
+ parentLookup.set(msg.messageId, msg);
+ }
+
+ const settled = new Set();
+ for (const message of messages) {
+ const chain = new Set();
+ let current = message;
+ while (current && !settled.has(current.messageId)) {
+ if (chain.has(current.messageId)) {
+ current.parentMessageId = Constants.NO_PARENT;
+ break;
+ }
+ chain.add(current.messageId);
+ const parentId = current.parentMessageId;
+ if (!parentId || parentId === Constants.NO_PARENT) {
+ break;
+ }
+ current = parentLookup.get(parentId);
+ }
+ for (const id of chain) {
+ settled.add(id);
+ }
+ }
}
module.exports = { getImporter, processAssistantMessage };
From 1ecff83b20f637cb95b2d07686c8abe38f472466 Mon Sep 17 00:00:00 2001
From: Danny Avila
Date: Thu, 19 Mar 2026 17:46:14 -0400
Subject: [PATCH 13/98] =?UTF-8?q?=F0=9F=AA=A6=20fix:=20ACL-Safe=20User=20A?=
=?UTF-8?q?ccount=20Deletion=20for=20Agents,=20Prompts,=20and=20MCP=20Serv?=
=?UTF-8?q?ers=20(#12314)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* fix: use ACL ownership for prompt group cleanup on user deletion
deleteUserPrompts previously called getAllPromptGroups with only an
author filter, which defaults to searchShared=true and drops the
author filter for shared/global project entries. This caused any user
deleting their account to strip shared prompt group associations and
ACL entries for other users.
Replace the author-based query with ACL-based ownership lookup:
- Find prompt groups where the user has OWNER permission (DELETE bit)
- Only delete groups where the user is the sole owner
- Preserve multi-owned groups and their ACL entries for other owners
* fix: use ACL ownership for agent cleanup on user deletion
deleteUserAgents used the deprecated author field to find and delete
agents, then unconditionally removed all ACL entries for those agents.
This could destroy ACL entries for agents shared with or co-owned by
other users.
Replace the author-based query with ACL-based ownership lookup:
- Find agents where the user has OWNER permission (DELETE bit)
- Only delete agents where the user is the sole owner
- Preserve multi-owned agents and their ACL entries for other owners
- Also clean up handoff edges referencing deleted agents
* fix: add MCP server cleanup on user deletion
User deletion had no cleanup for MCP servers, leaving solely-owned
servers orphaned in the database with dangling ACL entries for other
users.
Add deleteUserMcpServers that follows the same ACL ownership pattern
as prompt groups and agents: find servers with OWNER permission,
check for sole ownership, and only delete those with no other owners.
* style: fix prettier formatting in Prompt.spec.js
* refactor: extract getSoleOwnedResourceIds to PermissionService
The ACL sole-ownership detection algorithm was duplicated across
deleteUserPrompts, deleteUserAgents, and deleteUserMcpServers.
Centralizes the three-step pattern (find owned entries, find other
owners, compute sole-owned set) into a single reusable utility.
* refactor: use getSoleOwnedResourceIds in all deletion functions
- Replace inline ACL queries with the centralized utility
- Remove vestigial _req parameter from deleteUserPrompts
- Use Promise.all for parallel project removal instead of sequential awaits
- Disconnect live MCP sessions and invalidate tool cache before deleting
sole-owned MCP server documents
- Export deleteUserMcpServers for testability
* test: improve deletion test coverage and quality
- Move deleteUserPrompts call to beforeAll to eliminate execution-order
dependency between tests
- Standardize on test() instead of it() for consistency in Prompt.spec.js
- Add assertion for deleting user's own ACL entry preservation on
multi-owned agents
- Add deleteUserMcpServers integration test suite with 6 tests covering
sole-owner deletion, multi-owner preservation, session disconnect,
cache invalidation, model-not-registered guard, and missing MCPManager
- Add PermissionService mock to existing deleteUser.spec.js to fix
import chain
* fix: add legacy author-based fallback for unmigrated resources
Resources created before the ACL system have author set but no AclEntry
records. The sole-ownership detection returns empty for these, causing
deleteUserPrompts, deleteUserAgents, and deleteUserMcpServers to silently
skip them — permanently orphaning data on user deletion.
Add a fallback that identifies author-owned resources with zero ACL
entries (truly unmigrated) and includes them in the deletion set. This
preserves the multi-owner safety of the ACL path while ensuring pre-ACL
resources are still cleaned up regardless of migration status.
* style: fix prettier formatting across all changed files
* test: add resource type coverage guard for user deletion
Ensures every ResourceType in the ACL system has a corresponding cleanup
handler wired into deleteUserController. When a new ResourceType is added
(e.g. WORKFLOW), this test fails immediately, preventing silent data
orphaning on user account deletion.
* style: fix import order in PermissionService destructure
* test: add opt-out set and fix test lifecycle in coverage guard
Add NO_USER_CLEANUP_NEEDED set for resource types that legitimately
require no per-user deletion. Move fs.readFileSync into beforeAll so
path errors surface as clean test failures instead of unhandled crashes.
---
api/models/Agent.js | 65 +++-
api/models/Agent.spec.js | 199 +++++++++--
api/models/Prompt.js | 49 ++-
api/models/Prompt.spec.js | 227 +++++++++++++
api/server/controllers/UserController.js | 86 ++++-
.../controllers/__tests__/deleteUser.spec.js | 4 +
.../__tests__/deleteUserMcpServers.spec.js | 319 ++++++++++++++++++
.../deleteUserResourceCoverage.spec.js | 53 +++
api/server/services/PermissionService.js | 51 ++-
9 files changed, 993 insertions(+), 60 deletions(-)
create mode 100644 api/server/controllers/__tests__/deleteUserMcpServers.spec.js
create mode 100644 api/server/controllers/__tests__/deleteUserResourceCoverage.spec.js
diff --git a/api/models/Agent.js b/api/models/Agent.js
index 663285183a..53098888d6 100644
--- a/api/models/Agent.js
+++ b/api/models/Agent.js
@@ -17,7 +17,10 @@ const {
removeAgentIdsFromProject,
addAgentIdsToProject,
} = require('./Project');
-const { removeAllPermissions } = require('~/server/services/PermissionService');
+const {
+ getSoleOwnedResourceIds,
+ removeAllPermissions,
+} = require('~/server/services/PermissionService');
const { getMCPServerTools } = require('~/server/services/Config');
const { Agent, AclEntry, User } = require('~/db/models');
const { getActions } = require('./Action');
@@ -617,30 +620,70 @@ const deleteAgent = async (searchParameter) => {
};
/**
- * Deletes all agents created by a specific user.
+ * Deletes agents solely owned by the user and cleans up their ACLs/project references.
+ * Agents with other owners are left intact; the caller is responsible for
+ * removing the user's own ACL principal entries separately.
+ *
+ * Also handles legacy (pre-ACL) agents that only have the author field set,
+ * ensuring they are not orphaned if no permission migration has been run.
* @param {string} userId - The ID of the user whose agents should be deleted.
- * @returns {Promise} A promise that resolves when all user agents have been deleted.
+ * @returns {Promise}
*/
const deleteUserAgents = async (userId) => {
try {
- const userAgents = await getAgents({ author: userId });
+ const userObjectId = new mongoose.Types.ObjectId(userId);
+ const soleOwnedObjectIds = await getSoleOwnedResourceIds(userObjectId, [
+ ResourceType.AGENT,
+ ResourceType.REMOTE_AGENT,
+ ]);
- if (userAgents.length === 0) {
+ const authoredAgents = await Agent.find({ author: userObjectId }).select('id _id').lean();
+
+ const migratedEntries =
+ authoredAgents.length > 0
+ ? await AclEntry.find({
+ resourceType: { $in: [ResourceType.AGENT, ResourceType.REMOTE_AGENT] },
+ resourceId: { $in: authoredAgents.map((a) => a._id) },
+ })
+ .select('resourceId')
+ .lean()
+ : [];
+ const migratedIds = new Set(migratedEntries.map((e) => e.resourceId.toString()));
+ const legacyAgents = authoredAgents.filter((a) => !migratedIds.has(a._id.toString()));
+
+ /** resourceId is the MongoDB _id; agent.id is the string identifier for project/edge queries */
+ const soleOwnedAgents =
+ soleOwnedObjectIds.length > 0
+ ? await Agent.find({ _id: { $in: soleOwnedObjectIds } })
+ .select('id _id')
+ .lean()
+ : [];
+
+ const allAgents = [...soleOwnedAgents, ...legacyAgents];
+
+ if (allAgents.length === 0) {
return;
}
- const agentIds = userAgents.map((agent) => agent.id);
- const agentObjectIds = userAgents.map((agent) => agent._id);
+ const agentIds = allAgents.map((agent) => agent.id);
+ const agentObjectIds = allAgents.map((agent) => agent._id);
- for (const agentId of agentIds) {
- await removeAgentFromAllProjects(agentId);
- }
+ await Promise.all(agentIds.map((id) => removeAgentFromAllProjects(id)));
await AclEntry.deleteMany({
resourceType: { $in: [ResourceType.AGENT, ResourceType.REMOTE_AGENT] },
resourceId: { $in: agentObjectIds },
});
+ try {
+ await Agent.updateMany(
+ { 'edges.to': { $in: agentIds } },
+ { $pull: { edges: { to: { $in: agentIds } } } },
+ );
+ } catch (error) {
+ logger.error('[deleteUserAgents] Error removing agents from handoff edges', error);
+ }
+
try {
await User.updateMany(
{ 'favorites.agentId': { $in: agentIds } },
@@ -650,7 +693,7 @@ const deleteUserAgents = async (userId) => {
logger.error('[deleteUserAgents] Error removing agents from user favorites', error);
}
- await Agent.deleteMany({ author: userId });
+ await Agent.deleteMany({ _id: { $in: agentObjectIds } });
} catch (error) {
logger.error('[deleteUserAgents] General error:', error);
}
diff --git a/api/models/Agent.spec.js b/api/models/Agent.spec.js
index baceb3e8f3..b2597872ab 100644
--- a/api/models/Agent.spec.js
+++ b/api/models/Agent.spec.js
@@ -15,7 +15,12 @@ const mongoose = require('mongoose');
const { v4: uuidv4 } = require('uuid');
const { agentSchema } = require('@librechat/data-schemas');
const { MongoMemoryServer } = require('mongodb-memory-server');
-const { AccessRoleIds, ResourceType, PrincipalType } = require('librechat-data-provider');
+const {
+ ResourceType,
+ AccessRoleIds,
+ PrincipalType,
+ PermissionBits,
+} = require('librechat-data-provider');
const {
getAgent,
loadAgent,
@@ -442,6 +447,7 @@ describe('models/Agent', () => {
beforeEach(async () => {
await Agent.deleteMany({});
+ await AclEntry.deleteMany({});
});
test('should create and get an agent', async () => {
@@ -838,8 +844,7 @@ describe('models/Agent', () => {
const agent2Id = `agent_${uuidv4()}`;
const otherAuthorAgentId = `agent_${uuidv4()}`;
- // Create agents by the author to be deleted
- await createAgent({
+ const agent1 = await createAgent({
id: agent1Id,
name: 'Author Agent 1',
provider: 'test',
@@ -847,7 +852,7 @@ describe('models/Agent', () => {
author: authorId,
});
- await createAgent({
+ const agent2 = await createAgent({
id: agent2Id,
name: 'Author Agent 2',
provider: 'test',
@@ -855,7 +860,6 @@ describe('models/Agent', () => {
author: authorId,
});
- // Create agent by different author (should not be deleted)
await createAgent({
id: otherAuthorAgentId,
name: 'Other Author Agent',
@@ -864,7 +868,23 @@ describe('models/Agent', () => {
author: otherAuthorId,
});
- // Create user with all agents in favorites
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: authorId,
+ resourceType: ResourceType.AGENT,
+ resourceId: agent1._id,
+ accessRoleId: AccessRoleIds.AGENT_OWNER,
+ grantedBy: authorId,
+ });
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: authorId,
+ resourceType: ResourceType.AGENT,
+ resourceId: agent2._id,
+ accessRoleId: AccessRoleIds.AGENT_OWNER,
+ grantedBy: authorId,
+ });
+
await User.create({
_id: userId,
name: 'Test User',
@@ -878,21 +898,16 @@ describe('models/Agent', () => {
],
});
- // Verify user has all favorites
const userBefore = await User.findById(userId);
expect(userBefore.favorites).toHaveLength(4);
- // Delete all agents by the author
await deleteUserAgents(authorId.toString());
- // Verify author's agents are deleted from database
expect(await getAgent({ id: agent1Id })).toBeNull();
expect(await getAgent({ id: agent2Id })).toBeNull();
- // Verify other author's agent still exists
expect(await getAgent({ id: otherAuthorAgentId })).not.toBeNull();
- // Verify user favorites: author's agents removed, others remain
const userAfter = await User.findById(userId);
expect(userAfter.favorites).toHaveLength(2);
expect(userAfter.favorites.some((f) => f.agentId === agent1Id)).toBe(false);
@@ -911,8 +926,7 @@ describe('models/Agent', () => {
const agent2Id = `agent_${uuidv4()}`;
const unrelatedAgentId = `agent_${uuidv4()}`;
- // Create agents by the author
- await createAgent({
+ const agent1 = await createAgent({
id: agent1Id,
name: 'Author Agent 1',
provider: 'test',
@@ -920,7 +934,7 @@ describe('models/Agent', () => {
author: authorId,
});
- await createAgent({
+ const agent2 = await createAgent({
id: agent2Id,
name: 'Author Agent 2',
provider: 'test',
@@ -928,7 +942,23 @@ describe('models/Agent', () => {
author: authorId,
});
- // Create users with various favorites configurations
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: authorId,
+ resourceType: ResourceType.AGENT,
+ resourceId: agent1._id,
+ accessRoleId: AccessRoleIds.AGENT_OWNER,
+ grantedBy: authorId,
+ });
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: authorId,
+ resourceType: ResourceType.AGENT,
+ resourceId: agent2._id,
+ accessRoleId: AccessRoleIds.AGENT_OWNER,
+ grantedBy: authorId,
+ });
+
await User.create({
_id: user1Id,
name: 'User 1',
@@ -953,10 +983,8 @@ describe('models/Agent', () => {
favorites: [{ agentId: unrelatedAgentId }, { model: 'gpt-4', endpoint: 'openAI' }],
});
- // Delete all agents by the author
await deleteUserAgents(authorId.toString());
- // Verify all users' favorites are correctly updated
const user1After = await User.findById(user1Id);
expect(user1After.favorites).toHaveLength(0);
@@ -965,7 +993,6 @@ describe('models/Agent', () => {
expect(user2After.favorites.some((f) => f.agentId === agent1Id)).toBe(false);
expect(user2After.favorites.some((f) => f.model === 'claude-3')).toBe(true);
- // User 3 should be completely unaffected
const user3After = await User.findById(user3Id);
expect(user3After.favorites).toHaveLength(2);
expect(user3After.favorites.some((f) => f.agentId === unrelatedAgentId)).toBe(true);
@@ -979,8 +1006,7 @@ describe('models/Agent', () => {
const existingAgentId = `agent_${uuidv4()}`;
- // Create agent by different author
- await createAgent({
+ const existingAgent = await createAgent({
id: existingAgentId,
name: 'Existing Agent',
provider: 'test',
@@ -988,7 +1014,15 @@ describe('models/Agent', () => {
author: otherAuthorId,
});
- // Create user with favorites
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: otherAuthorId,
+ resourceType: ResourceType.AGENT,
+ resourceId: existingAgent._id,
+ accessRoleId: AccessRoleIds.AGENT_OWNER,
+ grantedBy: otherAuthorId,
+ });
+
await User.create({
_id: userId,
name: 'Test User',
@@ -997,13 +1031,10 @@ describe('models/Agent', () => {
favorites: [{ agentId: existingAgentId }, { model: 'gpt-4', endpoint: 'openAI' }],
});
- // Delete agents for user with no agents (should be a no-op)
await deleteUserAgents(authorWithNoAgentsId.toString());
- // Verify existing agent still exists
expect(await getAgent({ id: existingAgentId })).not.toBeNull();
- // Verify user favorites are unchanged
const userAfter = await User.findById(userId);
expect(userAfter.favorites).toHaveLength(2);
expect(userAfter.favorites.some((f) => f.agentId === existingAgentId)).toBe(true);
@@ -1017,8 +1048,7 @@ describe('models/Agent', () => {
const agent1Id = `agent_${uuidv4()}`;
const agent2Id = `agent_${uuidv4()}`;
- // Create agents by the author
- await createAgent({
+ const agent1 = await createAgent({
id: agent1Id,
name: 'Agent 1',
provider: 'test',
@@ -1026,7 +1056,7 @@ describe('models/Agent', () => {
author: authorId,
});
- await createAgent({
+ const agent2 = await createAgent({
id: agent2Id,
name: 'Agent 2',
provider: 'test',
@@ -1034,7 +1064,23 @@ describe('models/Agent', () => {
author: authorId,
});
- // Create user with favorites that don't include these agents
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: authorId,
+ resourceType: ResourceType.AGENT,
+ resourceId: agent1._id,
+ accessRoleId: AccessRoleIds.AGENT_OWNER,
+ grantedBy: authorId,
+ });
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: authorId,
+ resourceType: ResourceType.AGENT,
+ resourceId: agent2._id,
+ accessRoleId: AccessRoleIds.AGENT_OWNER,
+ grantedBy: authorId,
+ });
+
await User.create({
_id: userId,
name: 'Test User',
@@ -1043,23 +1089,112 @@ describe('models/Agent', () => {
favorites: [{ model: 'gpt-4', endpoint: 'openAI' }],
});
- // Verify agents exist
expect(await getAgent({ id: agent1Id })).not.toBeNull();
expect(await getAgent({ id: agent2Id })).not.toBeNull();
- // Delete all agents by the author
await deleteUserAgents(authorId.toString());
- // Verify agents are deleted
expect(await getAgent({ id: agent1Id })).toBeNull();
expect(await getAgent({ id: agent2Id })).toBeNull();
- // Verify user favorites are unchanged
const userAfter = await User.findById(userId);
expect(userAfter.favorites).toHaveLength(1);
expect(userAfter.favorites.some((f) => f.model === 'gpt-4')).toBe(true);
});
+ test('should preserve multi-owned agents when deleteUserAgents is called', async () => {
+ const deletingUserId = new mongoose.Types.ObjectId();
+ const otherOwnerId = new mongoose.Types.ObjectId();
+
+ const soleOwnedId = `agent_${uuidv4()}`;
+ const multiOwnedId = `agent_${uuidv4()}`;
+
+ const soleAgent = await createAgent({
+ id: soleOwnedId,
+ name: 'Sole Owned Agent',
+ provider: 'test',
+ model: 'test-model',
+ author: deletingUserId,
+ });
+
+ const multiAgent = await createAgent({
+ id: multiOwnedId,
+ name: 'Multi Owned Agent',
+ provider: 'test',
+ model: 'test-model',
+ author: deletingUserId,
+ });
+
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: deletingUserId,
+ resourceType: ResourceType.AGENT,
+ resourceId: soleAgent._id,
+ accessRoleId: AccessRoleIds.AGENT_OWNER,
+ grantedBy: deletingUserId,
+ });
+
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: deletingUserId,
+ resourceType: ResourceType.AGENT,
+ resourceId: multiAgent._id,
+ accessRoleId: AccessRoleIds.AGENT_OWNER,
+ grantedBy: deletingUserId,
+ });
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: otherOwnerId,
+ resourceType: ResourceType.AGENT,
+ resourceId: multiAgent._id,
+ accessRoleId: AccessRoleIds.AGENT_OWNER,
+ grantedBy: otherOwnerId,
+ });
+
+ await deleteUserAgents(deletingUserId.toString());
+
+ expect(await getAgent({ id: soleOwnedId })).toBeNull();
+ expect(await getAgent({ id: multiOwnedId })).not.toBeNull();
+
+ const soleAcl = await AclEntry.find({
+ resourceType: ResourceType.AGENT,
+ resourceId: soleAgent._id,
+ });
+ expect(soleAcl).toHaveLength(0);
+
+ const multiAcl = await AclEntry.find({
+ resourceType: ResourceType.AGENT,
+ resourceId: multiAgent._id,
+ principalId: otherOwnerId,
+ });
+ expect(multiAcl).toHaveLength(1);
+ expect(multiAcl[0].permBits & PermissionBits.DELETE).toBeTruthy();
+
+ const deletingUserMultiAcl = await AclEntry.find({
+ resourceType: ResourceType.AGENT,
+ resourceId: multiAgent._id,
+ principalId: deletingUserId,
+ });
+ expect(deletingUserMultiAcl).toHaveLength(1);
+ });
+
+ test('should delete legacy agents that have author but no ACL entries', async () => {
+ const legacyUserId = new mongoose.Types.ObjectId();
+ const legacyAgentId = `agent_${uuidv4()}`;
+
+ await createAgent({
+ id: legacyAgentId,
+ name: 'Legacy Agent (no ACL)',
+ provider: 'test',
+ model: 'test-model',
+ author: legacyUserId,
+ });
+
+ await deleteUserAgents(legacyUserId.toString());
+
+ expect(await getAgent({ id: legacyAgentId })).toBeNull();
+ });
+
test('should update agent projects', async () => {
const agentId = `agent_${uuidv4()}`;
const authorId = new mongoose.Types.ObjectId();
diff --git a/api/models/Prompt.js b/api/models/Prompt.js
index bde911b23a..4b14edbc74 100644
--- a/api/models/Prompt.js
+++ b/api/models/Prompt.js
@@ -13,7 +13,10 @@ const {
addGroupIdsToProject,
getProjectByName,
} = require('./Project');
-const { removeAllPermissions } = require('~/server/services/PermissionService');
+const {
+ getSoleOwnedResourceIds,
+ removeAllPermissions,
+} = require('~/server/services/PermissionService');
const { PromptGroup, Prompt, AclEntry } = require('~/db/models');
/**
@@ -592,31 +595,49 @@ module.exports = {
}
},
/**
- * Delete all prompts and prompt groups created by a specific user.
- * @param {ServerRequest} req - The server request object.
+ * Delete prompt groups solely owned by the user and clean up their prompts/ACLs.
+ * Groups with other owners are left intact; the caller is responsible for
+ * removing the user's own ACL principal entries separately.
+ *
+ * Also handles legacy (pre-ACL) prompt groups that only have the author field set,
+ * ensuring they are not orphaned if the permission migration has not been run.
* @param {string} userId - The ID of the user whose prompts and prompt groups are to be deleted.
*/
- deleteUserPrompts: async (req, userId) => {
+ deleteUserPrompts: async (userId) => {
try {
- const promptGroups = await getAllPromptGroups(req, { author: new ObjectId(userId) });
+ const userObjectId = new ObjectId(userId);
+ const soleOwnedIds = await getSoleOwnedResourceIds(userObjectId, ResourceType.PROMPTGROUP);
- if (promptGroups.length === 0) {
+ const authoredGroups = await PromptGroup.find({ author: userObjectId }).select('_id').lean();
+ const authoredGroupIds = authoredGroups.map((g) => g._id);
+
+ const migratedEntries =
+ authoredGroupIds.length > 0
+ ? await AclEntry.find({
+ resourceType: ResourceType.PROMPTGROUP,
+ resourceId: { $in: authoredGroupIds },
+ })
+ .select('resourceId')
+ .lean()
+ : [];
+ const migratedIds = new Set(migratedEntries.map((e) => e.resourceId.toString()));
+ const legacyGroupIds = authoredGroupIds.filter((id) => !migratedIds.has(id.toString()));
+
+ const allGroupIdsToDelete = [...soleOwnedIds, ...legacyGroupIds];
+
+ if (allGroupIdsToDelete.length === 0) {
return;
}
- const groupIds = promptGroups.map((group) => group._id);
-
- for (const groupId of groupIds) {
- await removeGroupFromAllProjects(groupId);
- }
+ await Promise.all(allGroupIdsToDelete.map((id) => removeGroupFromAllProjects(id)));
await AclEntry.deleteMany({
resourceType: ResourceType.PROMPTGROUP,
- resourceId: { $in: groupIds },
+ resourceId: { $in: allGroupIdsToDelete },
});
- await PromptGroup.deleteMany({ author: new ObjectId(userId) });
- await Prompt.deleteMany({ author: new ObjectId(userId) });
+ await PromptGroup.deleteMany({ _id: { $in: allGroupIdsToDelete } });
+ await Prompt.deleteMany({ groupId: { $in: allGroupIdsToDelete } });
} catch (error) {
logger.error('[deleteUserPrompts] General error:', error);
}
diff --git a/api/models/Prompt.spec.js b/api/models/Prompt.spec.js
index e00a1a518c..a2063e6cfc 100644
--- a/api/models/Prompt.spec.js
+++ b/api/models/Prompt.spec.js
@@ -561,4 +561,231 @@ describe('Prompt ACL Permissions', () => {
expect(prompt._id.toString()).toBe(legacyPrompt._id.toString());
});
});
+
+ describe('deleteUserPrompts', () => {
+ let deletingUser;
+ let otherUser;
+ let soleOwnedGroup;
+ let multiOwnedGroup;
+ let sharedGroup;
+ let soleOwnedPrompt;
+ let multiOwnedPrompt;
+ let sharedPrompt;
+
+ beforeAll(async () => {
+ deletingUser = await User.create({
+ name: 'Deleting User',
+ email: 'deleting@example.com',
+ role: SystemRoles.USER,
+ });
+ otherUser = await User.create({
+ name: 'Other User',
+ email: 'other@example.com',
+ role: SystemRoles.USER,
+ });
+
+ const soleProductionId = new ObjectId();
+ soleOwnedGroup = await PromptGroup.create({
+ name: 'Sole Owned Group',
+ author: deletingUser._id,
+ authorName: deletingUser.name,
+ productionId: soleProductionId,
+ });
+ soleOwnedPrompt = await Prompt.create({
+ prompt: 'Sole owned prompt',
+ author: deletingUser._id,
+ groupId: soleOwnedGroup._id,
+ type: 'text',
+ });
+ await PromptGroup.updateOne(
+ { _id: soleOwnedGroup._id },
+ { productionId: soleOwnedPrompt._id },
+ );
+
+ const multiProductionId = new ObjectId();
+ multiOwnedGroup = await PromptGroup.create({
+ name: 'Multi Owned Group',
+ author: deletingUser._id,
+ authorName: deletingUser.name,
+ productionId: multiProductionId,
+ });
+ multiOwnedPrompt = await Prompt.create({
+ prompt: 'Multi owned prompt',
+ author: deletingUser._id,
+ groupId: multiOwnedGroup._id,
+ type: 'text',
+ });
+ await PromptGroup.updateOne(
+ { _id: multiOwnedGroup._id },
+ { productionId: multiOwnedPrompt._id },
+ );
+
+ const sharedProductionId = new ObjectId();
+ sharedGroup = await PromptGroup.create({
+ name: 'Shared Group (other user owns)',
+ author: otherUser._id,
+ authorName: otherUser.name,
+ productionId: sharedProductionId,
+ });
+ sharedPrompt = await Prompt.create({
+ prompt: 'Shared prompt',
+ author: otherUser._id,
+ groupId: sharedGroup._id,
+ type: 'text',
+ });
+ await PromptGroup.updateOne({ _id: sharedGroup._id }, { productionId: sharedPrompt._id });
+
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: deletingUser._id,
+ resourceType: ResourceType.PROMPTGROUP,
+ resourceId: soleOwnedGroup._id,
+ accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
+ grantedBy: deletingUser._id,
+ });
+
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: deletingUser._id,
+ resourceType: ResourceType.PROMPTGROUP,
+ resourceId: multiOwnedGroup._id,
+ accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
+ grantedBy: deletingUser._id,
+ });
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: otherUser._id,
+ resourceType: ResourceType.PROMPTGROUP,
+ resourceId: multiOwnedGroup._id,
+ accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
+ grantedBy: otherUser._id,
+ });
+
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: otherUser._id,
+ resourceType: ResourceType.PROMPTGROUP,
+ resourceId: sharedGroup._id,
+ accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
+ grantedBy: otherUser._id,
+ });
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: deletingUser._id,
+ resourceType: ResourceType.PROMPTGROUP,
+ resourceId: sharedGroup._id,
+ accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
+ grantedBy: otherUser._id,
+ });
+
+ const globalProject = await Project.findOne({ name: 'Global' });
+ await Project.updateOne(
+ { _id: globalProject._id },
+ {
+ $addToSet: {
+ promptGroupIds: {
+ $each: [soleOwnedGroup._id, multiOwnedGroup._id, sharedGroup._id],
+ },
+ },
+ },
+ );
+
+ await promptFns.deleteUserPrompts(deletingUser._id.toString());
+ });
+
+ test('should delete solely-owned prompt groups and their prompts', async () => {
+ expect(await PromptGroup.findById(soleOwnedGroup._id)).toBeNull();
+ expect(await Prompt.findById(soleOwnedPrompt._id)).toBeNull();
+ });
+
+ test('should remove solely-owned groups from projects', async () => {
+ const globalProject = await Project.findOne({ name: 'Global' });
+ const projectGroupIds = globalProject.promptGroupIds.map((id) => id.toString());
+ expect(projectGroupIds).not.toContain(soleOwnedGroup._id.toString());
+ });
+
+ test('should remove all ACL entries for solely-owned groups', async () => {
+ const aclEntries = await AclEntry.find({
+ resourceType: ResourceType.PROMPTGROUP,
+ resourceId: soleOwnedGroup._id,
+ });
+ expect(aclEntries).toHaveLength(0);
+ });
+
+ test('should preserve multi-owned prompt groups', async () => {
+ expect(await PromptGroup.findById(multiOwnedGroup._id)).not.toBeNull();
+ expect(await Prompt.findById(multiOwnedPrompt._id)).not.toBeNull();
+ });
+
+ test('should preserve ACL entries of other owners on multi-owned groups', async () => {
+ const otherOwnerAcl = await AclEntry.findOne({
+ resourceType: ResourceType.PROMPTGROUP,
+ resourceId: multiOwnedGroup._id,
+ principalId: otherUser._id,
+ });
+ expect(otherOwnerAcl).not.toBeNull();
+ expect(otherOwnerAcl.permBits & PermissionBits.DELETE).toBeTruthy();
+ });
+
+ test('should preserve groups owned by other users', async () => {
+ expect(await PromptGroup.findById(sharedGroup._id)).not.toBeNull();
+ expect(await Prompt.findById(sharedPrompt._id)).not.toBeNull();
+ });
+
+ test('should preserve project membership of non-deleted groups', async () => {
+ const globalProject = await Project.findOne({ name: 'Global' });
+ const projectGroupIds = globalProject.promptGroupIds.map((id) => id.toString());
+ expect(projectGroupIds).toContain(multiOwnedGroup._id.toString());
+ expect(projectGroupIds).toContain(sharedGroup._id.toString());
+ });
+
+ test('should preserve ACL entries for shared group owned by other user', async () => {
+ const ownerAcl = await AclEntry.findOne({
+ resourceType: ResourceType.PROMPTGROUP,
+ resourceId: sharedGroup._id,
+ principalId: otherUser._id,
+ });
+ expect(ownerAcl).not.toBeNull();
+ });
+
+ test('should be a no-op when user has no owned prompt groups', async () => {
+ const unrelatedUser = await User.create({
+ name: 'Unrelated User',
+ email: 'unrelated@example.com',
+ role: SystemRoles.USER,
+ });
+
+ const beforeCount = await PromptGroup.countDocuments();
+ await promptFns.deleteUserPrompts(unrelatedUser._id.toString());
+ const afterCount = await PromptGroup.countDocuments();
+
+ expect(afterCount).toBe(beforeCount);
+ });
+
+ test('should delete legacy prompt groups that have author but no ACL entries', async () => {
+ const legacyUser = await User.create({
+ name: 'Legacy User',
+ email: 'legacy-prompt@example.com',
+ role: SystemRoles.USER,
+ });
+
+ const legacyGroup = await PromptGroup.create({
+ name: 'Legacy Group (no ACL)',
+ author: legacyUser._id,
+ authorName: legacyUser.name,
+ productionId: new ObjectId(),
+ });
+ const legacyPrompt = await Prompt.create({
+ prompt: 'Legacy prompt text',
+ author: legacyUser._id,
+ groupId: legacyGroup._id,
+ type: 'text',
+ });
+
+ await promptFns.deleteUserPrompts(legacyUser._id.toString());
+
+ expect(await PromptGroup.findById(legacyGroup._id)).toBeNull();
+ expect(await Prompt.findById(legacyPrompt._id)).toBeNull();
+ });
+ });
});
diff --git a/api/server/controllers/UserController.js b/api/server/controllers/UserController.js
index 6d5df0ac8d..51f6d218ec 100644
--- a/api/server/controllers/UserController.js
+++ b/api/server/controllers/UserController.js
@@ -1,11 +1,18 @@
+const mongoose = require('mongoose');
const { logger, webSearchKeys } = require('@librechat/data-schemas');
-const { Tools, CacheKeys, Constants, FileSources } = require('librechat-data-provider');
const {
MCPOAuthHandler,
MCPTokenStorage,
normalizeHttpError,
extractWebSearchEnvVars,
} = require('@librechat/api');
+const {
+ Tools,
+ CacheKeys,
+ Constants,
+ FileSources,
+ ResourceType,
+} = require('librechat-data-provider');
const {
deleteAllUserSessions,
deleteAllSharedLinks,
@@ -45,6 +52,7 @@ const { getAppConfig } = require('~/server/services/Config');
const { deleteToolCalls } = require('~/models/ToolCall');
const { deleteUserPrompts } = require('~/models/Prompt');
const { deleteUserAgents } = require('~/models/Agent');
+const { getSoleOwnedResourceIds } = require('~/server/services/PermissionService');
const { getLogStores } = require('~/cache');
const getUserController = async (req, res) => {
@@ -113,6 +121,78 @@ const deleteUserFiles = async (req) => {
}
};
+/**
+ * Deletes MCP servers solely owned by the user and cleans up their ACLs.
+ * Disconnects live sessions for deleted servers before removing DB records.
+ * Servers with other owners are left intact; the caller is responsible for
+ * removing the user's own ACL principal entries separately.
+ *
+ * Also handles legacy (pre-ACL) MCP servers that only have the author field set,
+ * ensuring they are not orphaned if no permission migration has been run.
+ * @param {string} userId - The ID of the user.
+ */
+const deleteUserMcpServers = async (userId) => {
+ try {
+ const MCPServer = mongoose.models.MCPServer;
+ if (!MCPServer) {
+ return;
+ }
+
+ const userObjectId = new mongoose.Types.ObjectId(userId);
+ const soleOwnedIds = await getSoleOwnedResourceIds(userObjectId, ResourceType.MCPSERVER);
+
+ const authoredServers = await MCPServer.find({ author: userObjectId })
+ .select('_id serverName')
+ .lean();
+
+ const migratedEntries =
+ authoredServers.length > 0
+ ? await AclEntry.find({
+ resourceType: ResourceType.MCPSERVER,
+ resourceId: { $in: authoredServers.map((s) => s._id) },
+ })
+ .select('resourceId')
+ .lean()
+ : [];
+ const migratedIds = new Set(migratedEntries.map((e) => e.resourceId.toString()));
+ const legacyServers = authoredServers.filter((s) => !migratedIds.has(s._id.toString()));
+ const legacyServerIds = legacyServers.map((s) => s._id);
+
+ const allServerIdsToDelete = [...soleOwnedIds, ...legacyServerIds];
+
+ if (allServerIdsToDelete.length === 0) {
+ return;
+ }
+
+ const aclOwnedServers =
+ soleOwnedIds.length > 0
+ ? await MCPServer.find({ _id: { $in: soleOwnedIds } })
+ .select('serverName')
+ .lean()
+ : [];
+ const allServersToDelete = [...aclOwnedServers, ...legacyServers];
+
+ const mcpManager = getMCPManager();
+ if (mcpManager) {
+ await Promise.all(
+ allServersToDelete.map(async (s) => {
+ await mcpManager.disconnectUserConnection(userId, s.serverName);
+ await invalidateCachedTools({ userId, serverName: s.serverName });
+ }),
+ );
+ }
+
+ await AclEntry.deleteMany({
+ resourceType: ResourceType.MCPSERVER,
+ resourceId: { $in: allServerIdsToDelete },
+ });
+
+ await MCPServer.deleteMany({ _id: { $in: allServerIdsToDelete } });
+ } catch (error) {
+ logger.error('[deleteUserMcpServers] General error:', error);
+ }
+};
+
const updateUserPluginsController = async (req, res) => {
const appConfig = await getAppConfig({ role: req.user?.role });
const { user } = req;
@@ -281,7 +361,8 @@ const deleteUserController = async (req, res) => {
await Assistant.deleteMany({ user: user.id }); // delete user assistants
await ConversationTag.deleteMany({ user: user.id }); // delete user conversation tags
await MemoryEntry.deleteMany({ userId: user.id }); // delete user memory entries
- await deleteUserPrompts(req, user.id); // delete user prompts
+ await deleteUserPrompts(user.id); // delete user prompts
+ await deleteUserMcpServers(user.id); // delete user MCP servers
await Action.deleteMany({ user: user.id }); // delete user actions
await Token.deleteMany({ userId: user.id }); // delete user OAuth tokens
await Group.updateMany(
@@ -439,4 +520,5 @@ module.exports = {
verifyEmailController,
updateUserPluginsController,
resendVerificationController,
+ deleteUserMcpServers,
};
diff --git a/api/server/controllers/__tests__/deleteUser.spec.js b/api/server/controllers/__tests__/deleteUser.spec.js
index d0f54a046f..6382cd1d8e 100644
--- a/api/server/controllers/__tests__/deleteUser.spec.js
+++ b/api/server/controllers/__tests__/deleteUser.spec.js
@@ -104,6 +104,10 @@ jest.mock('~/server/services/Config', () => ({
getAppConfig: jest.fn(),
}));
+jest.mock('~/server/services/PermissionService', () => ({
+ getSoleOwnedResourceIds: jest.fn().mockResolvedValue([]),
+}));
+
jest.mock('~/models/ToolCall', () => ({
deleteToolCalls: (...args) => mockDeleteToolCalls(...args),
}));
diff --git a/api/server/controllers/__tests__/deleteUserMcpServers.spec.js b/api/server/controllers/__tests__/deleteUserMcpServers.spec.js
new file mode 100644
index 0000000000..fcb3211f24
--- /dev/null
+++ b/api/server/controllers/__tests__/deleteUserMcpServers.spec.js
@@ -0,0 +1,319 @@
+const mockGetMCPManager = jest.fn();
+const mockInvalidateCachedTools = jest.fn();
+
+jest.mock('~/config', () => ({
+ getMCPManager: (...args) => mockGetMCPManager(...args),
+ getFlowStateManager: jest.fn(),
+ getMCPServersRegistry: jest.fn(),
+}));
+
+jest.mock('~/server/services/Config/getCachedTools', () => ({
+ invalidateCachedTools: (...args) => mockInvalidateCachedTools(...args),
+}));
+
+jest.mock('~/server/services/Config', () => ({
+ getAppConfig: jest.fn(),
+ getMCPServerTools: jest.fn(),
+}));
+
+const mongoose = require('mongoose');
+const { mcpServerSchema } = require('@librechat/data-schemas');
+const { MongoMemoryServer } = require('mongodb-memory-server');
+const {
+ ResourceType,
+ AccessRoleIds,
+ PrincipalType,
+ PermissionBits,
+} = require('librechat-data-provider');
+const permissionService = require('~/server/services/PermissionService');
+const { deleteUserMcpServers } = require('~/server/controllers/UserController');
+const { AclEntry, AccessRole } = require('~/db/models');
+
+let MCPServer;
+
+describe('deleteUserMcpServers', () => {
+ let mongoServer;
+
+ beforeAll(async () => {
+ mongoServer = await MongoMemoryServer.create();
+ const mongoUri = mongoServer.getUri();
+ MCPServer = mongoose.models.MCPServer || mongoose.model('MCPServer', mcpServerSchema);
+ await mongoose.connect(mongoUri);
+
+ await AccessRole.create({
+ accessRoleId: AccessRoleIds.MCPSERVER_OWNER,
+ name: 'MCP Server Owner',
+ resourceType: ResourceType.MCPSERVER,
+ permBits:
+ PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE | PermissionBits.SHARE,
+ });
+
+ await AccessRole.create({
+ accessRoleId: AccessRoleIds.MCPSERVER_VIEWER,
+ name: 'MCP Server Viewer',
+ resourceType: ResourceType.MCPSERVER,
+ permBits: PermissionBits.VIEW,
+ });
+ }, 20000);
+
+ afterAll(async () => {
+ await mongoose.disconnect();
+ await mongoServer.stop();
+ });
+
+ beforeEach(async () => {
+ await MCPServer.deleteMany({});
+ await AclEntry.deleteMany({});
+ jest.clearAllMocks();
+ });
+
+ test('should delete solely-owned MCP servers and their ACL entries', async () => {
+ const userId = new mongoose.Types.ObjectId();
+
+ const server = await MCPServer.create({
+ serverName: 'sole-owned-server',
+ config: { title: 'Test Server' },
+ author: userId,
+ });
+
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: userId,
+ resourceType: ResourceType.MCPSERVER,
+ resourceId: server._id,
+ accessRoleId: AccessRoleIds.MCPSERVER_OWNER,
+ grantedBy: userId,
+ });
+
+ mockGetMCPManager.mockReturnValue({
+ disconnectUserConnection: jest.fn().mockResolvedValue(undefined),
+ });
+
+ await deleteUserMcpServers(userId.toString());
+
+ expect(await MCPServer.findById(server._id)).toBeNull();
+
+ const aclEntries = await AclEntry.find({
+ resourceType: ResourceType.MCPSERVER,
+ resourceId: server._id,
+ });
+ expect(aclEntries).toHaveLength(0);
+ });
+
+ test('should disconnect MCP sessions and invalidate tool cache before deletion', async () => {
+ const userId = new mongoose.Types.ObjectId();
+ const mockDisconnect = jest.fn().mockResolvedValue(undefined);
+
+ const server = await MCPServer.create({
+ serverName: 'session-server',
+ config: { title: 'Session Server' },
+ author: userId,
+ });
+
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: userId,
+ resourceType: ResourceType.MCPSERVER,
+ resourceId: server._id,
+ accessRoleId: AccessRoleIds.MCPSERVER_OWNER,
+ grantedBy: userId,
+ });
+
+ mockGetMCPManager.mockReturnValue({ disconnectUserConnection: mockDisconnect });
+
+ await deleteUserMcpServers(userId.toString());
+
+ expect(mockDisconnect).toHaveBeenCalledWith(userId.toString(), 'session-server');
+ expect(mockInvalidateCachedTools).toHaveBeenCalledWith({
+ userId: userId.toString(),
+ serverName: 'session-server',
+ });
+ });
+
+ test('should preserve multi-owned MCP servers', async () => {
+ const deletingUserId = new mongoose.Types.ObjectId();
+ const otherOwnerId = new mongoose.Types.ObjectId();
+
+ const soleServer = await MCPServer.create({
+ serverName: 'sole-server',
+ config: { title: 'Sole Server' },
+ author: deletingUserId,
+ });
+
+ const multiServer = await MCPServer.create({
+ serverName: 'multi-server',
+ config: { title: 'Multi Server' },
+ author: deletingUserId,
+ });
+
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: deletingUserId,
+ resourceType: ResourceType.MCPSERVER,
+ resourceId: soleServer._id,
+ accessRoleId: AccessRoleIds.MCPSERVER_OWNER,
+ grantedBy: deletingUserId,
+ });
+
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: deletingUserId,
+ resourceType: ResourceType.MCPSERVER,
+ resourceId: multiServer._id,
+ accessRoleId: AccessRoleIds.MCPSERVER_OWNER,
+ grantedBy: deletingUserId,
+ });
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: otherOwnerId,
+ resourceType: ResourceType.MCPSERVER,
+ resourceId: multiServer._id,
+ accessRoleId: AccessRoleIds.MCPSERVER_OWNER,
+ grantedBy: otherOwnerId,
+ });
+
+ mockGetMCPManager.mockReturnValue({
+ disconnectUserConnection: jest.fn().mockResolvedValue(undefined),
+ });
+
+ await deleteUserMcpServers(deletingUserId.toString());
+
+ expect(await MCPServer.findById(soleServer._id)).toBeNull();
+ expect(await MCPServer.findById(multiServer._id)).not.toBeNull();
+
+ const soleAcl = await AclEntry.find({
+ resourceType: ResourceType.MCPSERVER,
+ resourceId: soleServer._id,
+ });
+ expect(soleAcl).toHaveLength(0);
+
+ const multiAclOther = await AclEntry.find({
+ resourceType: ResourceType.MCPSERVER,
+ resourceId: multiServer._id,
+ principalId: otherOwnerId,
+ });
+ expect(multiAclOther).toHaveLength(1);
+ expect(multiAclOther[0].permBits & PermissionBits.DELETE).toBeTruthy();
+
+ const multiAclDeleting = await AclEntry.find({
+ resourceType: ResourceType.MCPSERVER,
+ resourceId: multiServer._id,
+ principalId: deletingUserId,
+ });
+ expect(multiAclDeleting).toHaveLength(1);
+ });
+
+ test('should be a no-op when user has no owned MCP servers', async () => {
+ const userId = new mongoose.Types.ObjectId();
+
+ const otherUserId = new mongoose.Types.ObjectId();
+ const server = await MCPServer.create({
+ serverName: 'other-server',
+ config: { title: 'Other Server' },
+ author: otherUserId,
+ });
+
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: otherUserId,
+ resourceType: ResourceType.MCPSERVER,
+ resourceId: server._id,
+ accessRoleId: AccessRoleIds.MCPSERVER_OWNER,
+ grantedBy: otherUserId,
+ });
+
+ await deleteUserMcpServers(userId.toString());
+
+ expect(await MCPServer.findById(server._id)).not.toBeNull();
+ expect(mockGetMCPManager).not.toHaveBeenCalled();
+ });
+
+ test('should handle gracefully when MCPServer model is not registered', async () => {
+ const originalModel = mongoose.models.MCPServer;
+ delete mongoose.models.MCPServer;
+
+ try {
+ const userId = new mongoose.Types.ObjectId();
+ await expect(deleteUserMcpServers(userId.toString())).resolves.toBeUndefined();
+ } finally {
+ mongoose.models.MCPServer = originalModel;
+ }
+ });
+
+ test('should handle gracefully when MCPManager is not available', async () => {
+ const userId = new mongoose.Types.ObjectId();
+
+ const server = await MCPServer.create({
+ serverName: 'no-manager-server',
+ config: { title: 'No Manager Server' },
+ author: userId,
+ });
+
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: userId,
+ resourceType: ResourceType.MCPSERVER,
+ resourceId: server._id,
+ accessRoleId: AccessRoleIds.MCPSERVER_OWNER,
+ grantedBy: userId,
+ });
+
+ mockGetMCPManager.mockReturnValue(null);
+
+ await deleteUserMcpServers(userId.toString());
+
+ expect(await MCPServer.findById(server._id)).toBeNull();
+ });
+
+ test('should delete legacy MCP servers that have author but no ACL entries', async () => {
+ const legacyUserId = new mongoose.Types.ObjectId();
+
+ const legacyServer = await MCPServer.create({
+ serverName: 'legacy-server',
+ config: { title: 'Legacy Server' },
+ author: legacyUserId,
+ });
+
+ mockGetMCPManager.mockReturnValue({
+ disconnectUserConnection: jest.fn().mockResolvedValue(undefined),
+ });
+
+ await deleteUserMcpServers(legacyUserId.toString());
+
+ expect(await MCPServer.findById(legacyServer._id)).toBeNull();
+ });
+
+ test('should delete both ACL-owned and legacy servers in one call', async () => {
+ const userId = new mongoose.Types.ObjectId();
+
+ const aclServer = await MCPServer.create({
+ serverName: 'acl-server',
+ config: { title: 'ACL Server' },
+ author: userId,
+ });
+
+ await permissionService.grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: userId,
+ resourceType: ResourceType.MCPSERVER,
+ resourceId: aclServer._id,
+ accessRoleId: AccessRoleIds.MCPSERVER_OWNER,
+ grantedBy: userId,
+ });
+
+ const legacyServer = await MCPServer.create({
+ serverName: 'legacy-mixed-server',
+ config: { title: 'Legacy Mixed' },
+ author: userId,
+ });
+
+ mockGetMCPManager.mockReturnValue({
+ disconnectUserConnection: jest.fn().mockResolvedValue(undefined),
+ });
+
+ await deleteUserMcpServers(userId.toString());
+
+ expect(await MCPServer.findById(aclServer._id)).toBeNull();
+ expect(await MCPServer.findById(legacyServer._id)).toBeNull();
+ });
+});
diff --git a/api/server/controllers/__tests__/deleteUserResourceCoverage.spec.js b/api/server/controllers/__tests__/deleteUserResourceCoverage.spec.js
new file mode 100644
index 0000000000..b08e502800
--- /dev/null
+++ b/api/server/controllers/__tests__/deleteUserResourceCoverage.spec.js
@@ -0,0 +1,53 @@
+const fs = require('fs');
+const path = require('path');
+const { ResourceType } = require('librechat-data-provider');
+
+/**
+ * Maps each ResourceType to the cleanup function name that must appear in
+ * deleteUserController's source to prove it is handled during user deletion.
+ *
+ * When a new ResourceType is added, this test will fail until a corresponding
+ * entry is added here (or to NO_USER_CLEANUP_NEEDED) AND the actual cleanup
+ * logic is implemented.
+ */
+const HANDLED_RESOURCE_TYPES = {
+ [ResourceType.AGENT]: 'deleteUserAgents',
+ [ResourceType.REMOTE_AGENT]: 'deleteUserAgents',
+ [ResourceType.PROMPTGROUP]: 'deleteUserPrompts',
+ [ResourceType.MCPSERVER]: 'deleteUserMcpServers',
+};
+
+/**
+ * ResourceTypes that are ACL-tracked but have no per-user deletion semantics
+ * (e.g., system resources, public-only). Must be explicitly listed here with
+ * a justification to prevent silent omissions.
+ */
+const NO_USER_CLEANUP_NEEDED = new Set([
+ // Example: ResourceType.SYSTEM_TEMPLATE — public/system; not user-owned
+]);
+
+describe('deleteUserController - resource type coverage guard', () => {
+ let controllerSource;
+
+ beforeAll(() => {
+ controllerSource = fs.readFileSync(path.resolve(__dirname, '../UserController.js'), 'utf-8');
+ });
+
+ test('every ResourceType must have a documented cleanup handler or explicit exclusion', () => {
+ const allTypes = Object.values(ResourceType);
+ const handledTypes = Object.keys(HANDLED_RESOURCE_TYPES);
+ const unhandledTypes = allTypes.filter(
+ (t) => !handledTypes.includes(t) && !NO_USER_CLEANUP_NEEDED.has(t),
+ );
+
+ expect(unhandledTypes).toEqual([]);
+ });
+
+ test('every cleanup handler referenced in HANDLED_RESOURCE_TYPES must appear in the controller source', () => {
+ const uniqueHandlers = [...new Set(Object.values(HANDLED_RESOURCE_TYPES))];
+
+ for (const handler of uniqueHandlers) {
+ expect(controllerSource).toContain(handler);
+ }
+ });
+});
diff --git a/api/server/services/PermissionService.js b/api/server/services/PermissionService.js
index a843f48f6f..ba1ef68032 100644
--- a/api/server/services/PermissionService.js
+++ b/api/server/services/PermissionService.js
@@ -1,7 +1,12 @@
const mongoose = require('mongoose');
const { isEnabled } = require('@librechat/api');
const { getTransactionSupport, logger } = require('@librechat/data-schemas');
-const { ResourceType, PrincipalType, PrincipalModel } = require('librechat-data-provider');
+const {
+ ResourceType,
+ PrincipalType,
+ PrincipalModel,
+ PermissionBits,
+} = require('librechat-data-provider');
const {
entraIdPrincipalFeatureEnabled,
getUserOwnedEntraGroups,
@@ -799,6 +804,49 @@ const bulkUpdateResourcePermissions = async ({
}
};
+/**
+ * Returns resource IDs where the given user is the sole owner
+ * (no other principal holds the DELETE bit on the same resource).
+ * @param {mongoose.Types.ObjectId} userObjectId
+ * @param {string|string[]} resourceTypes - One or more ResourceType values.
+ * @returns {Promise}
+ */
+const getSoleOwnedResourceIds = async (userObjectId, resourceTypes) => {
+ const types = Array.isArray(resourceTypes) ? resourceTypes : [resourceTypes];
+ const ownedEntries = await AclEntry.find({
+ principalType: PrincipalType.USER,
+ principalId: userObjectId,
+ resourceType: { $in: types },
+ permBits: { $bitsAllSet: PermissionBits.DELETE },
+ })
+ .select('resourceId')
+ .lean();
+
+ if (ownedEntries.length === 0) {
+ return [];
+ }
+
+ const ownedIds = ownedEntries.map((e) => e.resourceId);
+
+ const otherOwners = await AclEntry.aggregate([
+ {
+ $match: {
+ resourceType: { $in: types },
+ resourceId: { $in: ownedIds },
+ permBits: { $bitsAllSet: PermissionBits.DELETE },
+ $or: [
+ { principalId: { $ne: userObjectId } },
+ { principalType: { $ne: PrincipalType.USER } },
+ ],
+ },
+ },
+ { $group: { _id: '$resourceId' } },
+ ]);
+
+ const multiOwnerIds = new Set(otherOwners.map((doc) => doc._id.toString()));
+ return ownedIds.filter((id) => !multiOwnerIds.has(id.toString()));
+};
+
/**
* Remove all permissions for a resource (cleanup when resource is deleted)
* @param {Object} params - Parameters for removing all permissions
@@ -839,5 +887,6 @@ module.exports = {
ensurePrincipalExists,
ensureGroupPrincipalExists,
syncUserEntraGroupMemberships,
+ getSoleOwnedResourceIds,
removeAllPermissions,
};
From 748fd086c1f7d943267d35cc5df2334cba13e26b Mon Sep 17 00:00:00 2001
From: Danny Avila
Date: Thu, 19 Mar 2026 18:09:23 -0400
Subject: [PATCH 14/98] =?UTF-8?q?=F0=9F=93=A6=20chore:=20Update=20`fast-xm?=
=?UTF-8?q?l-parser`=20to=20v5.5.7=20(#12317)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Bump fast-xml-parser dependency from 5.5.6 to 5.5.7 for improved functionality and compatibility.
- Update corresponding entries in both package.json and package-lock.json to reflect the new version.
---
package-lock.json | 8 ++++----
package.json | 6 +++---
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 0b0c0e4888..35aacac9c2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -27093,9 +27093,9 @@
}
},
"node_modules/fast-xml-parser": {
- "version": "5.5.6",
- "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.6.tgz",
- "integrity": "sha512-3+fdZyBRVg29n4rXP0joHthhcHdPUHaIC16cuyyd1iLsuaO6Vea36MPrxgAzbZna8lhvZeRL8Bc9GP56/J9xEw==",
+ "version": "5.5.7",
+ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.7.tgz",
+ "integrity": "sha512-LteOsISQ2GEiDHZch6L9hB0+MLoYVLToR7xotrzU0opCICBkxOPgHAy1HxAvtxfJNXDJpgAsQN30mkrfpO2Prg==",
"funding": [
{
"type": "github",
@@ -27106,7 +27106,7 @@
"dependencies": {
"fast-xml-builder": "^1.1.4",
"path-expression-matcher": "^1.1.3",
- "strnum": "^2.1.2"
+ "strnum": "^2.2.0"
},
"bin": {
"fxparser": "src/cli/cli.js"
diff --git a/package.json b/package.json
index e59032c7dd..de6a580a1a 100644
--- a/package.json
+++ b/package.json
@@ -139,13 +139,13 @@
"@librechat/agents": {
"@langchain/anthropic": {
"@anthropic-ai/sdk": "0.73.0",
- "fast-xml-parser": "5.5.6"
+ "fast-xml-parser": "5.5.7"
},
"@anthropic-ai/sdk": "0.73.0",
- "fast-xml-parser": "5.5.6"
+ "fast-xml-parser": "5.5.7"
},
"elliptic": "^6.6.1",
- "fast-xml-parser": "5.5.6",
+ "fast-xml-parser": "5.5.7",
"form-data": "^4.0.4",
"tslib": "^2.8.1",
"mdast-util-gfm-autolink-literal": "2.0.0",
From ecd6d76bc84d4960f3969fab0a056cd2f6e5a5bf Mon Sep 17 00:00:00 2001
From: Brad Russell
Date: Thu, 19 Mar 2026 21:48:03 -0400
Subject: [PATCH 15/98] =?UTF-8?q?=F0=9F=9A=A6=20fix:=20ERR=5FERL=5FINVALID?=
=?UTF-8?q?=5FIP=5FADDRESS=20and=20IPv6=20Key=20Collisions=20in=20IP=20Rat?=
=?UTF-8?q?e=20Limiters=20(#12319)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* fix: Add removePorts keyGenerator to all IP-based rate limiters
Six IP-based rate limiters are missing the `keyGenerator: removePorts`
option that is already used by the auth-related limiters (login,
register, resetPassword, verifyEmail). Without it, reverse proxies that
include ports in X-Forwarded-For headers cause
ERR_ERL_INVALID_IP_ADDRESS errors from express-rate-limit.
Fixes #12318
* fix: make removePorts IPv6-safe to prevent rate-limit key collisions
The original regex `/:\d+[^:]*$/` treated the last colon-delimited
segment of bare IPv6 addresses as a port, mangling valid IPs
(e.g. `::1` → `::`, `2001:db8::1` → `2001:db8::`). Distinct IPv6
clients could collapse into the same rate-limit bucket.
Use `net.isIP()` as a fast path for already-valid IPs, then match
bracketed IPv6+port and IPv4+port explicitly. Bare IPv6 addresses
are now returned unchanged.
Also fixes pre-existing property ordering inconsistency in
ttsLimiters.js userLimiterOptions (keyGenerator before store).
* refactor: move removePorts to packages/api as TypeScript, fix import order
- Move removePorts implementation to packages/api/src/utils/removePorts.ts
with proper Express Request typing
- Reduce api/server/utils/removePorts.js to a thin re-export from
@librechat/api for backward compatibility
- Consolidate removePorts import with limiterCache from @librechat/api
in all 6 limiter files, fixing import order (package imports shortest
to longest, local imports longest to shortest)
- Remove narrating inline comments per code style guidelines
---------
Co-authored-by: Danny Avila
---
api/cache/banViolation.js | 3 +-
api/server/middleware/checkBan.js | 5 +-
.../middleware/limiters/forkLimiters.js | 3 +-
.../middleware/limiters/importLimiters.js | 5 +-
.../middleware/limiters/loginLimiter.js | 3 +-
.../middleware/limiters/messageLimiters.js | 5 +-
.../middleware/limiters/registerLimiter.js | 3 +-
.../limiters/resetPasswordLimiter.js | 3 +-
api/server/middleware/limiters/sttLimiters.js | 5 +-
api/server/middleware/limiters/ttsLimiters.js | 7 +-
.../middleware/limiters/uploadLimiters.js | 5 +-
.../middleware/limiters/verifyEmailLimiter.js | 3 +-
api/server/utils/index.js | 2 -
api/server/utils/removePorts.js | 1 -
packages/api/src/utils/index.ts | 1 +
packages/api/src/utils/ports.spec.ts | 98 +++++++++++++++++++
packages/api/src/utils/ports.ts | 38 +++++++
17 files changed, 162 insertions(+), 28 deletions(-)
delete mode 100644 api/server/utils/removePorts.js
create mode 100644 packages/api/src/utils/ports.spec.ts
create mode 100644 packages/api/src/utils/ports.ts
diff --git a/api/cache/banViolation.js b/api/cache/banViolation.js
index 4d321889c1..36945ca420 100644
--- a/api/cache/banViolation.js
+++ b/api/cache/banViolation.js
@@ -1,8 +1,7 @@
const { logger } = require('@librechat/data-schemas');
-const { isEnabled, math } = require('@librechat/api');
const { ViolationTypes } = require('librechat-data-provider');
+const { isEnabled, math, removePorts } = require('@librechat/api');
const { deleteAllUserSessions } = require('~/models');
-const { removePorts } = require('~/server/utils');
const getLogStores = require('./getLogStores');
const { BAN_VIOLATIONS, BAN_INTERVAL } = process.env ?? {};
diff --git a/api/server/middleware/checkBan.js b/api/server/middleware/checkBan.js
index 79804a84e1..0c98f3a824 100644
--- a/api/server/middleware/checkBan.js
+++ b/api/server/middleware/checkBan.js
@@ -1,11 +1,10 @@
const { Keyv } = require('keyv');
const uap = require('ua-parser-js');
const { logger } = require('@librechat/data-schemas');
-const { isEnabled, keyvMongo } = require('@librechat/api');
const { ViolationTypes } = require('librechat-data-provider');
-const { removePorts } = require('~/server/utils');
-const denyRequest = require('./denyRequest');
+const { isEnabled, keyvMongo, removePorts } = require('@librechat/api');
const { getLogStores } = require('~/cache');
+const denyRequest = require('./denyRequest');
const { findUser } = require('~/models');
const banCache = new Keyv({ store: keyvMongo, namespace: ViolationTypes.BAN, ttl: 0 });
diff --git a/api/server/middleware/limiters/forkLimiters.js b/api/server/middleware/limiters/forkLimiters.js
index f1e9b15f11..6d05cedad5 100644
--- a/api/server/middleware/limiters/forkLimiters.js
+++ b/api/server/middleware/limiters/forkLimiters.js
@@ -1,6 +1,6 @@
const rateLimit = require('express-rate-limit');
-const { limiterCache } = require('@librechat/api');
const { ViolationTypes } = require('librechat-data-provider');
+const { limiterCache, removePorts } = require('@librechat/api');
const logViolation = require('~/cache/logViolation');
const getEnvironmentVariables = () => {
@@ -59,6 +59,7 @@ const createForkLimiters = () => {
windowMs: forkIpWindowMs,
max: forkIpMax,
handler: createForkHandler(),
+ keyGenerator: removePorts,
store: limiterCache('fork_ip_limiter'),
};
const userLimiterOptions = {
diff --git a/api/server/middleware/limiters/importLimiters.js b/api/server/middleware/limiters/importLimiters.js
index f383e99563..22b7013558 100644
--- a/api/server/middleware/limiters/importLimiters.js
+++ b/api/server/middleware/limiters/importLimiters.js
@@ -1,6 +1,6 @@
const rateLimit = require('express-rate-limit');
-const { limiterCache } = require('@librechat/api');
const { ViolationTypes } = require('librechat-data-provider');
+const { limiterCache, removePorts } = require('@librechat/api');
const logViolation = require('~/cache/logViolation');
const getEnvironmentVariables = () => {
@@ -60,6 +60,7 @@ const createImportLimiters = () => {
windowMs: importIpWindowMs,
max: importIpMax,
handler: createImportHandler(),
+ keyGenerator: removePorts,
store: limiterCache('import_ip_limiter'),
};
const userLimiterOptions = {
@@ -67,7 +68,7 @@ const createImportLimiters = () => {
max: importUserMax,
handler: createImportHandler(false),
keyGenerator: function (req) {
- return req.user?.id; // Use the user ID or NULL if not available
+ return req.user?.id;
},
store: limiterCache('import_user_limiter'),
};
diff --git a/api/server/middleware/limiters/loginLimiter.js b/api/server/middleware/limiters/loginLimiter.js
index eef0c56bfc..c178b68a25 100644
--- a/api/server/middleware/limiters/loginLimiter.js
+++ b/api/server/middleware/limiters/loginLimiter.js
@@ -1,7 +1,6 @@
const rateLimit = require('express-rate-limit');
-const { limiterCache } = require('@librechat/api');
const { ViolationTypes } = require('librechat-data-provider');
-const { removePorts } = require('~/server/utils');
+const { limiterCache, removePorts } = require('@librechat/api');
const { logViolation } = require('~/cache');
const { LOGIN_WINDOW = 5, LOGIN_MAX = 7, LOGIN_VIOLATION_SCORE: score } = process.env;
diff --git a/api/server/middleware/limiters/messageLimiters.js b/api/server/middleware/limiters/messageLimiters.js
index 50f4dbc644..4f1d72076f 100644
--- a/api/server/middleware/limiters/messageLimiters.js
+++ b/api/server/middleware/limiters/messageLimiters.js
@@ -1,6 +1,6 @@
const rateLimit = require('express-rate-limit');
-const { limiterCache } = require('@librechat/api');
const { ViolationTypes } = require('librechat-data-provider');
+const { limiterCache, removePorts } = require('@librechat/api');
const denyRequest = require('~/server/middleware/denyRequest');
const { logViolation } = require('~/cache');
@@ -50,6 +50,7 @@ const ipLimiterOptions = {
windowMs: ipWindowMs,
max: ipMax,
handler: createHandler(),
+ keyGenerator: removePorts,
store: limiterCache('message_ip_limiter'),
};
@@ -58,7 +59,7 @@ const userLimiterOptions = {
max: userMax,
handler: createHandler(false),
keyGenerator: function (req) {
- return req.user?.id; // Use the user ID or NULL if not available
+ return req.user?.id;
},
store: limiterCache('message_user_limiter'),
};
diff --git a/api/server/middleware/limiters/registerLimiter.js b/api/server/middleware/limiters/registerLimiter.js
index eeebebdb42..91ea027376 100644
--- a/api/server/middleware/limiters/registerLimiter.js
+++ b/api/server/middleware/limiters/registerLimiter.js
@@ -1,7 +1,6 @@
const rateLimit = require('express-rate-limit');
-const { limiterCache } = require('@librechat/api');
const { ViolationTypes } = require('librechat-data-provider');
-const { removePorts } = require('~/server/utils');
+const { limiterCache, removePorts } = require('@librechat/api');
const { logViolation } = require('~/cache');
const { REGISTER_WINDOW = 60, REGISTER_MAX = 5, REGISTRATION_VIOLATION_SCORE: score } = process.env;
diff --git a/api/server/middleware/limiters/resetPasswordLimiter.js b/api/server/middleware/limiters/resetPasswordLimiter.js
index d1dfe52a98..7feca47ca5 100644
--- a/api/server/middleware/limiters/resetPasswordLimiter.js
+++ b/api/server/middleware/limiters/resetPasswordLimiter.js
@@ -1,7 +1,6 @@
const rateLimit = require('express-rate-limit');
-const { limiterCache } = require('@librechat/api');
const { ViolationTypes } = require('librechat-data-provider');
-const { removePorts } = require('~/server/utils');
+const { limiterCache, removePorts } = require('@librechat/api');
const { logViolation } = require('~/cache');
const {
diff --git a/api/server/middleware/limiters/sttLimiters.js b/api/server/middleware/limiters/sttLimiters.js
index f2f47cf680..ded9040033 100644
--- a/api/server/middleware/limiters/sttLimiters.js
+++ b/api/server/middleware/limiters/sttLimiters.js
@@ -1,6 +1,6 @@
const rateLimit = require('express-rate-limit');
-const { limiterCache } = require('@librechat/api');
const { ViolationTypes } = require('librechat-data-provider');
+const { limiterCache, removePorts } = require('@librechat/api');
const logViolation = require('~/cache/logViolation');
const getEnvironmentVariables = () => {
@@ -54,6 +54,7 @@ const createSTTLimiters = () => {
windowMs: sttIpWindowMs,
max: sttIpMax,
handler: createSTTHandler(),
+ keyGenerator: removePorts,
store: limiterCache('stt_ip_limiter'),
};
@@ -62,7 +63,7 @@ const createSTTLimiters = () => {
max: sttUserMax,
handler: createSTTHandler(false),
keyGenerator: function (req) {
- return req.user?.id; // Use the user ID or NULL if not available
+ return req.user?.id;
},
store: limiterCache('stt_user_limiter'),
};
diff --git a/api/server/middleware/limiters/ttsLimiters.js b/api/server/middleware/limiters/ttsLimiters.js
index 41dd9a6ba5..7ded475230 100644
--- a/api/server/middleware/limiters/ttsLimiters.js
+++ b/api/server/middleware/limiters/ttsLimiters.js
@@ -1,6 +1,6 @@
const rateLimit = require('express-rate-limit');
-const { limiterCache } = require('@librechat/api');
const { ViolationTypes } = require('librechat-data-provider');
+const { limiterCache, removePorts } = require('@librechat/api');
const logViolation = require('~/cache/logViolation');
const getEnvironmentVariables = () => {
@@ -54,6 +54,7 @@ const createTTSLimiters = () => {
windowMs: ttsIpWindowMs,
max: ttsIpMax,
handler: createTTSHandler(),
+ keyGenerator: removePorts,
store: limiterCache('tts_ip_limiter'),
};
@@ -61,10 +62,10 @@ const createTTSLimiters = () => {
windowMs: ttsUserWindowMs,
max: ttsUserMax,
handler: createTTSHandler(false),
- store: limiterCache('tts_user_limiter'),
keyGenerator: function (req) {
- return req.user?.id; // Use the user ID or NULL if not available
+ return req.user?.id;
},
+ store: limiterCache('tts_user_limiter'),
};
const ttsIpLimiter = rateLimit(ipLimiterOptions);
diff --git a/api/server/middleware/limiters/uploadLimiters.js b/api/server/middleware/limiters/uploadLimiters.js
index df6987877c..8c878cfa86 100644
--- a/api/server/middleware/limiters/uploadLimiters.js
+++ b/api/server/middleware/limiters/uploadLimiters.js
@@ -1,6 +1,6 @@
const rateLimit = require('express-rate-limit');
-const { limiterCache } = require('@librechat/api');
const { ViolationTypes } = require('librechat-data-provider');
+const { limiterCache, removePorts } = require('@librechat/api');
const logViolation = require('~/cache/logViolation');
const getEnvironmentVariables = () => {
@@ -60,6 +60,7 @@ const createFileLimiters = () => {
windowMs: fileUploadIpWindowMs,
max: fileUploadIpMax,
handler: createFileUploadHandler(),
+ keyGenerator: removePorts,
store: limiterCache('file_upload_ip_limiter'),
};
@@ -68,7 +69,7 @@ const createFileLimiters = () => {
max: fileUploadUserMax,
handler: createFileUploadHandler(false),
keyGenerator: function (req) {
- return req.user?.id; // Use the user ID or NULL if not available
+ return req.user?.id;
},
store: limiterCache('file_upload_user_limiter'),
};
diff --git a/api/server/middleware/limiters/verifyEmailLimiter.js b/api/server/middleware/limiters/verifyEmailLimiter.js
index 006c4df656..5844686bf0 100644
--- a/api/server/middleware/limiters/verifyEmailLimiter.js
+++ b/api/server/middleware/limiters/verifyEmailLimiter.js
@@ -1,7 +1,6 @@
const rateLimit = require('express-rate-limit');
-const { limiterCache } = require('@librechat/api');
const { ViolationTypes } = require('librechat-data-provider');
-const { removePorts } = require('~/server/utils');
+const { limiterCache, removePorts } = require('@librechat/api');
const { logViolation } = require('~/cache');
const {
diff --git a/api/server/utils/index.js b/api/server/utils/index.js
index 918ab54f85..59cb71625f 100644
--- a/api/server/utils/index.js
+++ b/api/server/utils/index.js
@@ -1,4 +1,3 @@
-const removePorts = require('./removePorts');
const handleText = require('./handleText');
const sendEmail = require('./sendEmail');
const queue = require('./queue');
@@ -6,7 +5,6 @@ const files = require('./files');
module.exports = {
...handleText,
- removePorts,
sendEmail,
...files,
...queue,
diff --git a/api/server/utils/removePorts.js b/api/server/utils/removePorts.js
deleted file mode 100644
index 375ff1cc71..0000000000
--- a/api/server/utils/removePorts.js
+++ /dev/null
@@ -1 +0,0 @@
-module.exports = (req) => req?.ip?.replace(/:\d+[^:]*$/, '');
diff --git a/packages/api/src/utils/index.ts b/packages/api/src/utils/index.ts
index a1412e21f2..3320fef949 100644
--- a/packages/api/src/utils/index.ts
+++ b/packages/api/src/utils/index.ts
@@ -18,6 +18,7 @@ export * from './math';
export * from './oidc';
export * from './openid';
export * from './promise';
+export * from './ports';
export * from './sanitizeTitle';
export * from './tempChatRetention';
export * from './text';
diff --git a/packages/api/src/utils/ports.spec.ts b/packages/api/src/utils/ports.spec.ts
new file mode 100644
index 0000000000..ea4dc284c7
--- /dev/null
+++ b/packages/api/src/utils/ports.spec.ts
@@ -0,0 +1,98 @@
+import type { Request } from 'express';
+import { removePorts } from './ports';
+
+const req = (ip: string | undefined): Request => ({ ip }) as Request;
+
+describe('removePorts', () => {
+ describe('bare IPv4 (no port)', () => {
+ test('returns a standard private IP unchanged', () => {
+ expect(removePorts(req('192.168.1.1'))).toBe('192.168.1.1');
+ });
+
+ test('returns a public IP unchanged', () => {
+ expect(removePorts(req('149.154.20.46'))).toBe('149.154.20.46');
+ });
+
+ test('returns loopback unchanged', () => {
+ expect(removePorts(req('127.0.0.1'))).toBe('127.0.0.1');
+ });
+ });
+
+ describe('IPv4 with port (the primary bug scenario)', () => {
+ test('strips port from a private IP', () => {
+ expect(removePorts(req('192.168.1.1:8080'))).toBe('192.168.1.1');
+ });
+
+ test('strips port from the IP in the original issue report', () => {
+ expect(removePorts(req('149.154.20.46:48198'))).toBe('149.154.20.46');
+ });
+
+ test('strips a low port number', () => {
+ expect(removePorts(req('10.0.0.1:80'))).toBe('10.0.0.1');
+ });
+
+ test('strips a high port number', () => {
+ expect(removePorts(req('10.0.0.1:65535'))).toBe('10.0.0.1');
+ });
+ });
+
+ describe('bare IPv6 (no port)', () => {
+ test('returns loopback unchanged', () => {
+ expect(removePorts(req('::1'))).toBe('::1');
+ });
+
+ test('returns a full address unchanged', () => {
+ expect(removePorts(req('2001:db8::1'))).toBe('2001:db8::1');
+ });
+
+ test('returns an IPv4-mapped IPv6 address unchanged', () => {
+ expect(removePorts(req('::ffff:192.168.1.1'))).toBe('::ffff:192.168.1.1');
+ });
+
+ test('returns a fully expanded IPv6 unchanged', () => {
+ expect(removePorts(req('2001:0db8:85a3:0000:0000:8a2e:0370:7334'))).toBe(
+ '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
+ );
+ });
+ });
+
+ describe('bracketed IPv6 with port', () => {
+ test('extracts loopback from brackets with port', () => {
+ expect(removePorts(req('[::1]:8080'))).toBe('::1');
+ });
+
+ test('extracts a full address from brackets with port', () => {
+ expect(removePorts(req('[2001:db8::1]:443'))).toBe('2001:db8::1');
+ });
+
+ test('extracts address from brackets without port', () => {
+ expect(removePorts(req('[::1]'))).toBe('::1');
+ });
+ });
+
+ describe('falsy / missing ip', () => {
+ test('returns undefined when ip is undefined', () => {
+ expect(removePorts(req(undefined))).toBeUndefined();
+ });
+
+ test('returns undefined when ip is empty string', () => {
+ expect(removePorts({ ip: '' } as Request)).toBe('');
+ });
+
+ test('returns undefined when req is null', () => {
+ expect(removePorts(null as unknown as Request)).toBeUndefined();
+ });
+ });
+
+ describe('IPv4-mapped IPv6 with port', () => {
+ test('strips port from an IPv4-mapped IPv6 address', () => {
+ expect(removePorts(req('::ffff:1.2.3.4:8080'))).toBe('::ffff:1.2.3.4');
+ });
+ });
+
+ describe('unrecognized formats fall through unchanged', () => {
+ test('returns garbage input unchanged', () => {
+ expect(removePorts(req('not-an-ip'))).toBe('not-an-ip');
+ });
+ });
+});
diff --git a/packages/api/src/utils/ports.ts b/packages/api/src/utils/ports.ts
new file mode 100644
index 0000000000..ecd38039d6
--- /dev/null
+++ b/packages/api/src/utils/ports.ts
@@ -0,0 +1,38 @@
+import type { Request } from 'express';
+
+/** Strips port suffix from req.ip for use as a rate-limiter key (IPv4 and IPv6-safe) */
+export function removePorts(req: Request): string | undefined {
+ const ip = req?.ip;
+ if (!ip) {
+ return ip;
+ }
+
+ if (ip.charCodeAt(0) === 91) {
+ const close = ip.indexOf(']');
+ return close > 0 ? ip.slice(1, close) : ip;
+ }
+
+ const lastColon = ip.lastIndexOf(':');
+ if (lastColon === -1) {
+ return ip;
+ }
+
+ if (ip.indexOf('.') !== -1 && hasOnlyDigitsAfter(ip, lastColon + 1)) {
+ return ip.slice(0, lastColon);
+ }
+
+ return ip;
+}
+
+function hasOnlyDigitsAfter(str: string, start: number): boolean {
+ if (start >= str.length) {
+ return false;
+ }
+ for (let i = start; i < str.length; i++) {
+ const c = str.charCodeAt(i);
+ if (c < 48 || c > 57) {
+ return false;
+ }
+ }
+ return true;
+}
From e442984364db02163f3cc3ecb7b2ee5efba66fb9 Mon Sep 17 00:00:00 2001
From: Danny Avila
Date: Thu, 19 Mar 2026 22:13:40 -0400
Subject: [PATCH 16/98] =?UTF-8?q?=F0=9F=92=A3=20fix:=20Harden=20against=20?=
=?UTF-8?q?falsified=20ZIP=20metadata=20in=20ODT=20parsing=20(#12320)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* security: replace JSZip metadata guard with yauzl streaming decompression
The ODT decompressed-size guard was checking JSZip's private
_data.uncompressedSize fields, which are populated from the ZIP central
directory — attacker-controlled metadata. A crafted ODT with falsified
uncompressedSize values bypassed the 50MB cap entirely, allowing
content.xml decompression to exhaust Node.js heap memory (DoS).
Replace JSZip with yauzl for ODT extraction. The new extractOdtContentXml
function uses yauzl's streaming API: it lazily iterates ZIP entries,
opens a decompression stream for content.xml, and counts real bytes as
they arrive from the inflate stream. The stream is destroyed the moment
the byte count crosses ODT_MAX_DECOMPRESSED_SIZE, aborting the inflate
before the full payload is materialised in memory.
- Remove jszip from direct dependencies (still transitive via mammoth)
- Add yauzl + @types/yauzl
- Update zip-bomb test to verify streaming abort with DEFLATE payload
* fix: close file descriptor leaks and declare jszip test dependency
- Use a shared `finish()` helper in extractOdtContentXml that calls
zipfile.close() on every exit path (success, size cap, missing entry,
openReadStream errors, zipfile errors). Without this, any error path
leaked one OS file descriptor permanently — uploading many malformed
ODTs could exhaust the process FD limit (a distinct DoS vector).
- Add jszip to devDependencies so the zip-bomb test has an explicit
dependency rather than relying on mammoth's transitive jszip.
- Update JSDoc to document that all exit paths close the zipfile.
* fix: move yauzl from dependencies to peerDependencies
Matches the established pattern for runtime parser libraries in
packages/api: mammoth, pdfjs-dist, and xlsx are all peerDependencies
(provided by the consuming /api workspace) with devDependencies for
testing. yauzl was incorrectly placed in dependencies.
* fix: add yauzl to /api dependencies to satisfy peer dep
packages/api declares yauzl as a peerDependency; /api is the consuming
workspace that must provide it at runtime, matching the pattern used
for mammoth, pdfjs-dist, and xlsx.
---
api/package.json | 1 +
package-lock.json | 23 ++--
packages/api/package.json | 9 +-
packages/api/src/files/documents/crud.spec.ts | 4 +-
packages/api/src/files/documents/crud.ts | 107 ++++++++++++++----
5 files changed, 108 insertions(+), 36 deletions(-)
diff --git a/api/package.json b/api/package.json
index 2255679dae..4416acd1d8 100644
--- a/api/package.json
+++ b/api/package.json
@@ -113,6 +113,7 @@
"winston": "^3.11.0",
"winston-daily-rotate-file": "^5.0.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
+ "yauzl": "^3.2.1",
"zod": "^3.22.4"
},
"devDependencies": {
diff --git a/package-lock.json b/package-lock.json
index 35aacac9c2..5d8264f602 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -128,6 +128,7 @@
"winston": "^3.11.0",
"winston-daily-rotate-file": "^5.0.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
+ "yauzl": "^3.2.1",
"zod": "^3.22.4"
},
"devDependencies": {
@@ -21269,6 +21270,16 @@
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
"dev": true
},
+ "node_modules/@types/yauzl": {
+ "version": "2.10.3",
+ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
+ "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.24.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.0.tgz",
@@ -23080,7 +23091,6 @@
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": "*"
@@ -35393,7 +35403,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
- "dev": true,
"license": "MIT"
},
"node_modules/picocolors": {
@@ -43740,7 +43749,6 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.1.tgz",
"integrity": "sha512-k1isifdbpNSFEHFJ1ZY4YDewv0IH9FR61lDetaRMD3j2ae3bIXGV+7c+LHCqtQGofSd8PIyV4X6+dHMAnSr60A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"buffer-crc32": "~0.2.3",
@@ -43802,9 +43810,6 @@
"name": "@librechat/api",
"version": "1.7.26",
"license": "ISC",
- "dependencies": {
- "jszip": "^3.10.1"
- },
"devDependencies": {
"@babel/preset-env": "^7.21.5",
"@babel/preset-react": "^7.18.6",
@@ -43825,8 +43830,10 @@
"@types/node-fetch": "^2.6.13",
"@types/react": "^18.2.18",
"@types/winston": "^2.4.4",
+ "@types/yauzl": "^2.10.3",
"jest": "^30.2.0",
"jest-junit": "^16.0.0",
+ "jszip": "^3.10.1",
"librechat-data-provider": "*",
"mammoth": "^1.11.0",
"mongodb": "^6.14.2",
@@ -43836,7 +43843,8 @@
"rollup-plugin-peer-deps-external": "^2.2.4",
"ts-node": "^10.9.2",
"typescript": "^5.0.4",
- "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
+ "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
+ "yauzl": "^3.2.1"
},
"peerDependencies": {
"@anthropic-ai/vertex-sdk": "^0.14.3",
@@ -43876,6 +43884,7 @@
"pdfjs-dist": "^5.4.624",
"rate-limit-redis": "^4.2.0",
"undici": "^7.24.1",
+ "yauzl": "^3.2.1",
"zod": "^3.22.4"
}
},
diff --git a/packages/api/package.json b/packages/api/package.json
index 57675ee371..3a3b3caef6 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -64,7 +64,9 @@
"@types/node-fetch": "^2.6.13",
"@types/react": "^18.2.18",
"@types/winston": "^2.4.4",
+ "@types/yauzl": "^2.10.3",
"jest": "^30.2.0",
+ "jszip": "^3.10.1",
"jest-junit": "^16.0.0",
"librechat-data-provider": "*",
"mammoth": "^1.11.0",
@@ -75,7 +77,8 @@
"rollup-plugin-peer-deps-external": "^2.2.4",
"ts-node": "^10.9.2",
"typescript": "^5.0.4",
- "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
+ "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
+ "yauzl": "^3.2.1"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/"
@@ -118,9 +121,7 @@
"pdfjs-dist": "^5.4.624",
"rate-limit-redis": "^4.2.0",
"undici": "^7.24.1",
+ "yauzl": "^3.2.1",
"zod": "^3.22.4"
- },
- "dependencies": {
- "jszip": "^3.10.1"
}
}
diff --git a/packages/api/src/files/documents/crud.spec.ts b/packages/api/src/files/documents/crud.spec.ts
index a1c317279c..2a5086869f 100644
--- a/packages/api/src/files/documents/crud.spec.ts
+++ b/packages/api/src/files/documents/crud.spec.ts
@@ -104,7 +104,7 @@ describe('Document Parser', () => {
await expect(parseDocument({ file })).rejects.toThrow('No text found in document');
});
- test('parseDocument() throws for odt whose decompressed content exceeds the size limit', async () => {
+ test('parseDocument() aborts decompression when content.xml exceeds the size limit', async () => {
const zip = new JSZip();
zip.file('mimetype', 'application/vnd.oasis.opendocument.text', { compression: 'STORE' });
zip.file('content.xml', 'x'.repeat(51 * 1024 * 1024), { compression: 'DEFLATE' });
@@ -118,7 +118,7 @@ describe('Document Parser', () => {
path: tmpPath,
mimetype: 'application/vnd.oasis.opendocument.text',
} as Express.Multer.File;
- await expect(parseDocument({ file })).rejects.toThrow(/exceeds the 50MB limit/);
+ await expect(parseDocument({ file })).rejects.toThrow(/exceeds the 50MB decompressed limit/);
} finally {
await fs.promises.unlink(tmpPath);
}
diff --git a/packages/api/src/files/documents/crud.ts b/packages/api/src/files/documents/crud.ts
index e255323f77..20d547cf26 100644
--- a/packages/api/src/files/documents/crud.ts
+++ b/packages/api/src/files/documents/crud.ts
@@ -1,5 +1,5 @@
import * as fs from 'fs';
-import JSZip from 'jszip';
+import yauzl from 'yauzl';
import { megabyte, excelMimeTypes, FileSources } from 'librechat-data-provider';
import type { TextItem } from 'pdfjs-dist/types/src/display/api';
import type { MistralOCRUploadResult } from '~/types';
@@ -124,28 +124,7 @@ async function excelSheetToText(file: Express.Multer.File): Promise {
* text boxes, and annotations are stripped without replacement.
*/
async function odtToText(file: Express.Multer.File): Promise {
- const data = await fs.promises.readFile(file.path);
- const zip = await JSZip.loadAsync(data);
-
- let totalUncompressed = 0;
- zip.forEach((_, entry) => {
- const raw = entry as JSZip.JSZipObject & { _data?: { uncompressedSize?: number } };
- // _data.uncompressedSize is populated from the ZIP central directory at parse time
- // by jszip (private internal, jszip@3.x). If the field is absent the guard fails
- // open (adds 0); this is an accepted limitation of the approach.
- totalUncompressed += raw._data?.uncompressedSize ?? 0;
- });
- if (totalUncompressed > ODT_MAX_DECOMPRESSED_SIZE) {
- throw new Error(
- `ODT file decompressed content (${Math.ceil(totalUncompressed / megabyte)}MB) exceeds the ${ODT_MAX_DECOMPRESSED_SIZE / megabyte}MB limit`,
- );
- }
-
- const contentFile = zip.file('content.xml');
- if (!contentFile) {
- throw new Error('ODT file is missing content.xml');
- }
- const xml = await contentFile.async('string');
+ const xml = await extractOdtContentXml(file.path);
const bodyMatch = xml.match(/]*>([\s\S]*?)<\/office:body>/);
if (!bodyMatch) {
return '';
@@ -168,3 +147,85 @@ async function odtToText(file: Express.Multer.File): Promise {
.replace(/\n{3,}/g, '\n\n')
.trim();
}
+
+/**
+ * Streams content.xml out of an ODT ZIP archive using yauzl, counting real
+ * decompressed bytes and aborting mid-inflate if the cap is exceeded.
+ * Unlike JSZip metadata checks, this cannot be bypassed by falsifying
+ * the ZIP central directory's uncompressedSize fields.
+ *
+ * The zipfile is closed on all exit paths (success, size cap, missing entry,
+ * error) to prevent file descriptor leaks.
+ */
+function extractOdtContentXml(filePath: string): Promise {
+ return new Promise((resolve, reject) => {
+ yauzl.open(filePath, { lazyEntries: true }, (err, zipfile) => {
+ if (err) {
+ return reject(err);
+ }
+ if (!zipfile) {
+ return reject(new Error('Failed to open ODT file'));
+ }
+
+ let settled = false;
+ const finish = (error: Error | null, result?: string) => {
+ if (settled) {
+ return;
+ }
+ settled = true;
+ zipfile.close();
+ if (error) {
+ reject(error);
+ } else {
+ resolve(result as string);
+ }
+ };
+
+ let found = false;
+ zipfile.readEntry();
+
+ zipfile.on('entry', (entry: yauzl.Entry) => {
+ if (entry.fileName !== 'content.xml') {
+ zipfile.readEntry();
+ return;
+ }
+ found = true;
+ zipfile.openReadStream(entry, (streamErr, readStream) => {
+ if (streamErr) {
+ return finish(streamErr);
+ }
+ if (!readStream) {
+ return finish(new Error('Failed to open content.xml stream'));
+ }
+
+ let totalBytes = 0;
+ const chunks: Buffer[] = [];
+
+ readStream.on('data', (chunk: Buffer) => {
+ totalBytes += chunk.byteLength;
+ if (totalBytes > ODT_MAX_DECOMPRESSED_SIZE) {
+ readStream.destroy(
+ new Error(
+ `ODT content.xml exceeds the ${ODT_MAX_DECOMPRESSED_SIZE / megabyte}MB decompressed limit`,
+ ),
+ );
+ return;
+ }
+ chunks.push(chunk);
+ });
+
+ readStream.on('end', () => finish(null, Buffer.concat(chunks).toString('utf8')));
+ readStream.on('error', (readErr: Error) => finish(readErr));
+ });
+ });
+
+ zipfile.on('end', () => {
+ if (!found) {
+ finish(new Error('ODT file is missing content.xml'));
+ }
+ });
+
+ zipfile.on('error', (zipErr: Error) => finish(zipErr));
+ });
+ });
+}
From 594d9470d58c13843d499f126b37c1a88638f9f3 Mon Sep 17 00:00:00 2001
From: Danny Avila
Date: Fri, 20 Mar 2026 12:32:55 -0400
Subject: [PATCH 17/98] =?UTF-8?q?=F0=9F=AA=A4=20fix:=20Avoid=20express-rat?=
=?UTF-8?q?e-limit=20v8=20ERR=5FERL=5FKEY=5FGEN=5FIPV6=20False=20Positive?=
=?UTF-8?q?=20(#12333)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* fix: avoid express-rate-limit v8 ERR_ERL_KEY_GEN_IPV6 false positive
express-rate-limit v8 calls keyGenerator.toString() and throws
ERR_ERL_KEY_GEN_IPV6 if the source contains the literal substring
"req.ip" without "ipKeyGenerator". When packages/api compiles
req?.ip to older JS targets, the output contains "req.ip",
triggering the heuristic.
Bracket notation (req?.['ip']) produces identical runtime behavior
but never emits the literal "req.ip" substring regardless of
compilation target.
Closes #12321
* fix: add toString regression test and clean up redundant annotation
Add a test that verifies removePorts.toString() does not contain
"req.ip", guarding against reintroduction of the ERR_ERL_KEY_GEN_IPV6
false positive. Fix a misleading test description and remove a
redundant type annotation on a trivially-inferred local.
---
packages/api/src/utils/ports.spec.ts | 8 +++++++-
packages/api/src/utils/ports.ts | 8 ++++++--
2 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/packages/api/src/utils/ports.spec.ts b/packages/api/src/utils/ports.spec.ts
index ea4dc284c7..0a53c867ea 100644
--- a/packages/api/src/utils/ports.spec.ts
+++ b/packages/api/src/utils/ports.spec.ts
@@ -75,7 +75,7 @@ describe('removePorts', () => {
expect(removePorts(req(undefined))).toBeUndefined();
});
- test('returns undefined when ip is empty string', () => {
+ test('returns empty string when ip is empty string', () => {
expect(removePorts({ ip: '' } as Request)).toBe('');
});
@@ -90,6 +90,12 @@ describe('removePorts', () => {
});
});
+ describe('express-rate-limit v8 heuristic guard', () => {
+ test('function source does not contain "req.ip" (guards against ERR_ERL_KEY_GEN_IPV6)', () => {
+ expect(removePorts.toString()).not.toContain('req.ip');
+ });
+ });
+
describe('unrecognized formats fall through unchanged', () => {
test('returns garbage input unchanged', () => {
expect(removePorts(req('not-an-ip'))).toBe('not-an-ip');
diff --git a/packages/api/src/utils/ports.ts b/packages/api/src/utils/ports.ts
index ecd38039d6..ed20c89193 100644
--- a/packages/api/src/utils/ports.ts
+++ b/packages/api/src/utils/ports.ts
@@ -1,8 +1,12 @@
import type { Request } from 'express';
-/** Strips port suffix from req.ip for use as a rate-limiter key (IPv4 and IPv6-safe) */
+/**
+ * Strips port suffix from req.ip for use as a rate-limiter key (IPv4 and IPv6-safe).
+ * Bracket notation for the ip property avoids express-rate-limit v8's toString()
+ * heuristic that scans for the literal substring "req.ip" (ERR_ERL_KEY_GEN_IPV6).
+ */
export function removePorts(req: Request): string | undefined {
- const ip = req?.ip;
+ const ip = req?.['ip'];
if (!ip) {
return ip;
}
From 96f6976e0039cce3be092d05198ef35830921034 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Airam=20Hern=C3=A1ndez=20Hern=C3=A1ndez?=
<100208966+Airamhh@users.noreply.github.com>
Date: Fri, 20 Mar 2026 16:46:57 +0000
Subject: [PATCH 18/98] =?UTF-8?q?=F0=9F=AA=82=20fix:=20Automatic=20`logout?=
=?UTF-8?q?=5Fhint`=20Fallback=20for=20Oversized=20OpenID=20Token=20URLs?=
=?UTF-8?q?=20(#12326)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* fix: automatic logout_hint fallback for long OpenID tokens
Implements OIDC RP-Initiated Logout cascading strategy to prevent errors when id_token_hint makes logout URL too long.
Automatically detects URLs exceeding configurable length and falls back to logout_hint only when URL is too long, preserving previous behavior when token is missing. Adds OPENID_MAX_LOGOUT_URL_LENGTH environment variable. Comprehensive test coverage with 20 tests. Works with any OpenID provider.
* fix: address review findings for OIDC logout URL length fallback
- Replace two-boolean tri-state (useIdTokenHint/urlTooLong) with a single
string discriminant ('use_token'|'too_long'|'no_token') for clarity
- Fix misleading warning: differentiate 'url too long + no client_id' from
'no token + no client_id' so operators get actionable advice
- Strict env var parsing: reject partial numeric strings like '500abc' that
Number.parseInt silently accepted; use regex + Number() instead
- Pre-compute projected URL length from base URL + token length (JWT chars
are URL-safe), eliminating the set-then-delete mutation pattern
- Extract parseMaxLogoutUrlLength helper for validation and early return
- Add tests: invalid env values, url-too-long + missing OPENID_CLIENT_ID,
boundary condition (exact max vs max+1), cookie-sourced long token
- Remove redundant try/finally in 'respects custom limit' test
- Use empty value in .env.example to signal optional config (default: 2000)
---------
Co-authored-by: Airam Hernández Hernández
Co-authored-by: Danny Avila
---
.env.example | 2 +
.../controllers/auth/LogoutController.js | 78 ++++-
.../controllers/auth/LogoutController.spec.js | 310 +++++++++++++++++-
3 files changed, 379 insertions(+), 11 deletions(-)
diff --git a/.env.example b/.env.example
index e746737ea4..73e95c394c 100644
--- a/.env.example
+++ b/.env.example
@@ -540,6 +540,8 @@ OPENID_ON_BEHALF_FLOW_USERINFO_SCOPE="user.read" # example for Scope Needed for
OPENID_USE_END_SESSION_ENDPOINT=
# URL to redirect to after OpenID logout (defaults to ${DOMAIN_CLIENT}/login)
OPENID_POST_LOGOUT_REDIRECT_URI=
+# Maximum logout URL length before using logout_hint instead of id_token_hint (default: 2000)
+OPENID_MAX_LOGOUT_URL_LENGTH=
#========================#
# SharePoint Integration #
diff --git a/api/server/controllers/auth/LogoutController.js b/api/server/controllers/auth/LogoutController.js
index 039ed630c2..381bfc58b2 100644
--- a/api/server/controllers/auth/LogoutController.js
+++ b/api/server/controllers/auth/LogoutController.js
@@ -4,11 +4,27 @@ const { logger } = require('@librechat/data-schemas');
const { logoutUser } = require('~/server/services/AuthService');
const { getOpenIdConfig } = require('~/strategies');
+/** Parses and validates OPENID_MAX_LOGOUT_URL_LENGTH, returning defaultValue on invalid input */
+function parseMaxLogoutUrlLength(defaultValue = 2000) {
+ const raw = process.env.OPENID_MAX_LOGOUT_URL_LENGTH;
+ const trimmed = raw == null ? '' : raw.trim();
+ if (trimmed === '') {
+ return defaultValue;
+ }
+ const parsed = /^\d+$/.test(trimmed) ? Number(trimmed) : NaN;
+ if (!Number.isFinite(parsed) || parsed <= 0) {
+ logger.warn(
+ `[logoutController] Invalid OPENID_MAX_LOGOUT_URL_LENGTH value "${raw}", using default ${defaultValue}`,
+ );
+ return defaultValue;
+ }
+ return parsed;
+}
+
const logoutController = async (req, res) => {
const parsedCookies = req.headers.cookie ? cookies.parse(req.headers.cookie) : {};
const isOpenIdUser = req.user?.openidId != null && req.user?.provider === 'openid';
- /** For OpenID users, read tokens from session (with cookie fallback) */
let refreshToken;
let idToken;
if (isOpenIdUser && req.session?.openidTokens) {
@@ -44,22 +60,64 @@ const logoutController = async (req, res) => {
const endSessionEndpoint = openIdConfig.serverMetadata().end_session_endpoint;
if (endSessionEndpoint) {
const endSessionUrl = new URL(endSessionEndpoint);
- /** Redirect back to app's login page after IdP logout */
const postLogoutRedirectUri =
process.env.OPENID_POST_LOGOUT_REDIRECT_URI || `${process.env.DOMAIN_CLIENT}/login`;
endSessionUrl.searchParams.set('post_logout_redirect_uri', postLogoutRedirectUri);
- /** Add id_token_hint (preferred) or client_id for OIDC spec compliance */
+ /**
+ * OIDC RP-Initiated Logout cascading strategy:
+ * 1. id_token_hint (most secure, identifies exact session)
+ * 2. logout_hint + client_id (when URL would exceed safe length)
+ * 3. client_id only (when no token available)
+ *
+ * JWT tokens from spec-compliant OIDC providers use base64url
+ * encoding (RFC 7515), whose characters are all URL-safe, so
+ * token length equals URL-encoded length for projection.
+ * Non-compliant issuers using standard base64 (+/=) will cause
+ * underestimation; increase OPENID_MAX_LOGOUT_URL_LENGTH if the
+ * fallback does not trigger as expected.
+ */
+ const maxLogoutUrlLength = parseMaxLogoutUrlLength();
+ let strategy = 'no_token';
if (idToken) {
+ const baseLength = endSessionUrl.toString().length;
+ const projectedLength = baseLength + '&id_token_hint='.length + idToken.length;
+ if (projectedLength > maxLogoutUrlLength) {
+ strategy = 'too_long';
+ logger.debug(
+ `[logoutController] Logout URL too long (${projectedLength} chars, max ${maxLogoutUrlLength}), ` +
+ 'switching to logout_hint strategy',
+ );
+ } else {
+ strategy = 'use_token';
+ }
+ }
+
+ if (strategy === 'use_token') {
endSessionUrl.searchParams.set('id_token_hint', idToken);
- } else if (process.env.OPENID_CLIENT_ID) {
- endSessionUrl.searchParams.set('client_id', process.env.OPENID_CLIENT_ID);
} else {
- logger.warn(
- '[logoutController] Neither id_token_hint nor OPENID_CLIENT_ID is available. ' +
- 'To enable id_token_hint, set OPENID_REUSE_TOKENS=true. ' +
- 'The OIDC end-session request may be rejected by the identity provider.',
- );
+ if (strategy === 'too_long') {
+ const logoutHint = req.user?.email || req.user?.username || req.user?.openidId;
+ if (logoutHint) {
+ endSessionUrl.searchParams.set('logout_hint', logoutHint);
+ }
+ }
+
+ if (process.env.OPENID_CLIENT_ID) {
+ endSessionUrl.searchParams.set('client_id', process.env.OPENID_CLIENT_ID);
+ } else if (strategy === 'too_long') {
+ logger.warn(
+ '[logoutController] Logout URL exceeds max length and OPENID_CLIENT_ID is not set. ' +
+ 'The OIDC end-session request may be rejected. ' +
+ 'Consider setting OPENID_CLIENT_ID or increasing OPENID_MAX_LOGOUT_URL_LENGTH.',
+ );
+ } else {
+ logger.warn(
+ '[logoutController] Neither id_token_hint nor OPENID_CLIENT_ID is available. ' +
+ 'To enable id_token_hint, set OPENID_REUSE_TOKENS=true. ' +
+ 'The OIDC end-session request may be rejected by the identity provider.',
+ );
+ }
}
response.redirect = endSessionUrl.toString();
diff --git a/api/server/controllers/auth/LogoutController.spec.js b/api/server/controllers/auth/LogoutController.spec.js
index 3f2a2de8e1..c9294fdcec 100644
--- a/api/server/controllers/auth/LogoutController.spec.js
+++ b/api/server/controllers/auth/LogoutController.spec.js
@@ -1,7 +1,7 @@
const cookies = require('cookie');
const mockLogoutUser = jest.fn();
-const mockLogger = { warn: jest.fn(), error: jest.fn() };
+const mockLogger = { warn: jest.fn(), error: jest.fn(), debug: jest.fn() };
const mockIsEnabled = jest.fn();
const mockGetOpenIdConfig = jest.fn();
@@ -256,4 +256,312 @@ describe('LogoutController', () => {
expect(res.clearCookie).toHaveBeenCalledWith('token_provider');
});
});
+
+ describe('URL length limit and logout_hint fallback', () => {
+ it('uses logout_hint when id_token makes URL exceed default limit (2000 chars)', async () => {
+ const longIdToken = 'a'.repeat(3000);
+ const req = buildReq({
+ user: { _id: 'user1', openidId: 'oid1', provider: 'openid', email: 'user@example.com' },
+ session: {
+ openidTokens: { refreshToken: 'srt', idToken: longIdToken },
+ destroy: jest.fn(),
+ },
+ });
+ const res = buildRes();
+
+ await logoutController(req, res);
+
+ const body = res.send.mock.calls[0][0];
+ expect(body.redirect).not.toContain('id_token_hint=');
+ expect(body.redirect).toContain('logout_hint=user%40example.com');
+ expect(body.redirect).toContain('client_id=my-client-id');
+ expect(mockLogger.debug).toHaveBeenCalledWith(expect.stringContaining('Logout URL too long'));
+ });
+
+ it('uses id_token_hint when URL is within default limit', async () => {
+ const shortIdToken = 'short-token';
+ const req = buildReq({
+ session: {
+ openidTokens: { refreshToken: 'srt', idToken: shortIdToken },
+ destroy: jest.fn(),
+ },
+ });
+ const res = buildRes();
+
+ await logoutController(req, res);
+
+ const body = res.send.mock.calls[0][0];
+ expect(body.redirect).toContain('id_token_hint=short-token');
+ expect(body.redirect).not.toContain('logout_hint=');
+ expect(body.redirect).not.toContain('client_id=');
+ });
+
+ it('respects custom OPENID_MAX_LOGOUT_URL_LENGTH', async () => {
+ process.env.OPENID_MAX_LOGOUT_URL_LENGTH = '500';
+ const mediumIdToken = 'a'.repeat(600);
+ const req = buildReq({
+ user: { _id: 'user1', openidId: 'oid1', provider: 'openid', email: 'user@example.com' },
+ session: {
+ openidTokens: { refreshToken: 'srt', idToken: mediumIdToken },
+ destroy: jest.fn(),
+ },
+ });
+ const res = buildRes();
+
+ await logoutController(req, res);
+
+ const body = res.send.mock.calls[0][0];
+ expect(body.redirect).not.toContain('id_token_hint=');
+ expect(body.redirect).toContain('logout_hint=user%40example.com');
+ });
+
+ it('uses username as logout_hint when email is not available', async () => {
+ const longIdToken = 'a'.repeat(3000);
+ const req = buildReq({
+ user: {
+ _id: 'user1',
+ openidId: 'oid1',
+ provider: 'openid',
+ username: 'testuser',
+ },
+ session: {
+ openidTokens: { refreshToken: 'srt', idToken: longIdToken },
+ destroy: jest.fn(),
+ },
+ });
+ const res = buildRes();
+
+ await logoutController(req, res);
+
+ const body = res.send.mock.calls[0][0];
+ expect(body.redirect).toContain('logout_hint=testuser');
+ });
+
+ it('uses openidId as logout_hint when email and username are not available', async () => {
+ const longIdToken = 'a'.repeat(3000);
+ const req = buildReq({
+ user: { _id: 'user1', openidId: 'unique-oid-123', provider: 'openid' },
+ session: {
+ openidTokens: { refreshToken: 'srt', idToken: longIdToken },
+ destroy: jest.fn(),
+ },
+ });
+ const res = buildRes();
+
+ await logoutController(req, res);
+
+ const body = res.send.mock.calls[0][0];
+ expect(body.redirect).toContain('logout_hint=unique-oid-123');
+ });
+
+ it('uses openidId as logout_hint when email and username are explicitly null', async () => {
+ const longIdToken = 'a'.repeat(3000);
+ const req = buildReq({
+ user: {
+ _id: 'user1',
+ openidId: 'oid-without-email',
+ provider: 'openid',
+ email: null,
+ username: null,
+ },
+ session: {
+ openidTokens: { refreshToken: 'srt', idToken: longIdToken },
+ destroy: jest.fn(),
+ },
+ });
+ const res = buildRes();
+
+ await logoutController(req, res);
+
+ const body = res.send.mock.calls[0][0];
+ expect(body.redirect).not.toContain('id_token_hint=');
+ expect(body.redirect).toContain('logout_hint=oid-without-email');
+ expect(body.redirect).toContain('client_id=my-client-id');
+ });
+
+ it('uses only client_id when absolutely no hint is available', async () => {
+ const longIdToken = 'a'.repeat(3000);
+ const req = buildReq({
+ user: {
+ _id: 'user1',
+ openidId: '',
+ provider: 'openid',
+ email: '',
+ username: '',
+ },
+ session: {
+ openidTokens: { refreshToken: 'srt', idToken: longIdToken },
+ destroy: jest.fn(),
+ },
+ });
+ const res = buildRes();
+
+ await logoutController(req, res);
+
+ const body = res.send.mock.calls[0][0];
+ expect(body.redirect).not.toContain('id_token_hint=');
+ expect(body.redirect).not.toContain('logout_hint=');
+ expect(body.redirect).toContain('client_id=my-client-id');
+ });
+
+ it('warns about missing OPENID_CLIENT_ID when URL is too long', async () => {
+ delete process.env.OPENID_CLIENT_ID;
+ const longIdToken = 'a'.repeat(3000);
+ const req = buildReq({
+ user: { _id: 'user1', openidId: 'oid1', provider: 'openid', email: 'user@example.com' },
+ session: {
+ openidTokens: { refreshToken: 'srt', idToken: longIdToken },
+ destroy: jest.fn(),
+ },
+ });
+ const res = buildRes();
+
+ await logoutController(req, res);
+
+ const body = res.send.mock.calls[0][0];
+ expect(body.redirect).not.toContain('id_token_hint=');
+ expect(body.redirect).toContain('logout_hint=');
+ expect(body.redirect).not.toContain('client_id=');
+ expect(mockLogger.warn).toHaveBeenCalledWith(
+ expect.stringContaining('OPENID_CLIENT_ID is not set'),
+ );
+ });
+
+ it('falls back to logout_hint for cookie-sourced long token', async () => {
+ const longCookieToken = 'a'.repeat(3000);
+ cookies.parse.mockReturnValue({
+ refreshToken: 'cookie-rt',
+ openid_id_token: longCookieToken,
+ });
+ const req = buildReq({
+ user: { _id: 'user1', openidId: 'oid1', provider: 'openid', email: 'user@example.com' },
+ session: { destroy: jest.fn() },
+ });
+ const res = buildRes();
+
+ await logoutController(req, res);
+
+ const body = res.send.mock.calls[0][0];
+ expect(body.redirect).not.toContain('id_token_hint=');
+ expect(body.redirect).toContain('logout_hint=user%40example.com');
+ expect(body.redirect).toContain('client_id=my-client-id');
+ });
+
+ it('keeps id_token_hint when projected URL length equals the max', async () => {
+ const baseUrl = new URL('https://idp.example.com/logout');
+ baseUrl.searchParams.set('post_logout_redirect_uri', 'https://app.example.com/login');
+ const baseLength = baseUrl.toString().length;
+ const tokenLength = 2000 - baseLength - '&id_token_hint='.length;
+ const exactToken = 'a'.repeat(tokenLength);
+
+ const req = buildReq({
+ session: {
+ openidTokens: { refreshToken: 'srt', idToken: exactToken },
+ destroy: jest.fn(),
+ },
+ });
+ const res = buildRes();
+
+ await logoutController(req, res);
+
+ const body = res.send.mock.calls[0][0];
+ expect(body.redirect).toContain('id_token_hint=');
+ expect(body.redirect).not.toContain('logout_hint=');
+ });
+
+ it('falls back to logout_hint when projected URL is one char over the max', async () => {
+ const baseUrl = new URL('https://idp.example.com/logout');
+ baseUrl.searchParams.set('post_logout_redirect_uri', 'https://app.example.com/login');
+ const baseLength = baseUrl.toString().length;
+ const tokenLength = 2000 - baseLength - '&id_token_hint='.length + 1;
+ const overToken = 'a'.repeat(tokenLength);
+
+ const req = buildReq({
+ user: { _id: 'user1', openidId: 'oid1', provider: 'openid', email: 'user@example.com' },
+ session: {
+ openidTokens: { refreshToken: 'srt', idToken: overToken },
+ destroy: jest.fn(),
+ },
+ });
+ const res = buildRes();
+
+ await logoutController(req, res);
+
+ const body = res.send.mock.calls[0][0];
+ expect(body.redirect).not.toContain('id_token_hint=');
+ expect(body.redirect).toContain('logout_hint=');
+ });
+ });
+
+ describe('invalid OPENID_MAX_LOGOUT_URL_LENGTH values', () => {
+ it('silently uses default when value is empty', async () => {
+ process.env.OPENID_MAX_LOGOUT_URL_LENGTH = '';
+ const req = buildReq();
+ const res = buildRes();
+
+ await logoutController(req, res);
+
+ expect(mockLogger.warn).not.toHaveBeenCalledWith(
+ expect.stringContaining('Invalid OPENID_MAX_LOGOUT_URL_LENGTH'),
+ );
+ const body = res.send.mock.calls[0][0];
+ expect(body.redirect).toContain('id_token_hint=small-id-token');
+ });
+
+ it('warns and uses default for partial numeric string', async () => {
+ process.env.OPENID_MAX_LOGOUT_URL_LENGTH = '500abc';
+ const req = buildReq();
+ const res = buildRes();
+
+ await logoutController(req, res);
+
+ expect(mockLogger.warn).toHaveBeenCalledWith(
+ expect.stringContaining('Invalid OPENID_MAX_LOGOUT_URL_LENGTH'),
+ );
+ const body = res.send.mock.calls[0][0];
+ expect(body.redirect).toContain('id_token_hint=small-id-token');
+ });
+
+ it('warns and uses default for zero value', async () => {
+ process.env.OPENID_MAX_LOGOUT_URL_LENGTH = '0';
+ const req = buildReq();
+ const res = buildRes();
+
+ await logoutController(req, res);
+
+ expect(mockLogger.warn).toHaveBeenCalledWith(
+ expect.stringContaining('Invalid OPENID_MAX_LOGOUT_URL_LENGTH'),
+ );
+ const body = res.send.mock.calls[0][0];
+ expect(body.redirect).toContain('id_token_hint=small-id-token');
+ });
+
+ it('warns and uses default for negative value', async () => {
+ process.env.OPENID_MAX_LOGOUT_URL_LENGTH = '-1';
+ const req = buildReq();
+ const res = buildRes();
+
+ await logoutController(req, res);
+
+ expect(mockLogger.warn).toHaveBeenCalledWith(
+ expect.stringContaining('Invalid OPENID_MAX_LOGOUT_URL_LENGTH'),
+ );
+ const body = res.send.mock.calls[0][0];
+ expect(body.redirect).toContain('id_token_hint=small-id-token');
+ });
+
+ it('warns and uses default for non-numeric string', async () => {
+ process.env.OPENID_MAX_LOGOUT_URL_LENGTH = 'abc';
+ const req = buildReq();
+ const res = buildRes();
+
+ await logoutController(req, res);
+
+ expect(mockLogger.warn).toHaveBeenCalledWith(
+ expect.stringContaining('Invalid OPENID_MAX_LOGOUT_URL_LENGTH'),
+ );
+ const body = res.send.mock.calls[0][0];
+ expect(body.redirect).toContain('id_token_hint=small-id-token');
+ });
+ });
});
From 54fc9c2c9958262e24967950d0e175d984388dd8 Mon Sep 17 00:00:00 2001
From: JooyoungChoi14 <63181822+JooyoungChoi14@users.noreply.github.com>
Date: Sat, 21 Mar 2026 01:47:51 +0900
Subject: [PATCH 19/98] =?UTF-8?q?=E2=99=BE=EF=B8=8F=20fix:=20Permanent=20B?=
=?UTF-8?q?an=20Cache=20and=20Expired=20Ban=20Cleanup=20Defects=20(#12324)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* fix: preserve ban data object in checkBan to prevent permanent cache
The !! operator on line 108 coerces the ban data object to a boolean,
losing the expiresAt property. This causes:
1. Number(true.expiresAt) = NaN → expired bans never cleaned from banLogs
2. banCache.set(key, true, NaN) → Keyv stores with expires: null (permanent)
3. IP-based cache entries persist indefinitely, blocking unrelated users
Fix: replace isBanned (boolean) with banData (original object) so that
expiresAt is accessible for TTL calculation and proper cache expiry.
* fix: address checkBan cleanup defects exposed by ban data fix
The prior commit correctly replaced boolean coercion with the ban data
object, but activated previously-dead cleanup code with several defects:
- IP-only expired bans fell through cleanup without returning next(),
re-caching with negative TTL (permanent entry) and blocking the user
- Redis deployments used cache-prefixed keys for banLogs.delete(),
silently failing since bans are stored at raw keys
- banCache.set() calls were fire-and-forget, silently dropping errors
- No guard for missing/invalid expiresAt reproduced the NaN TTL bug
on legacy ban records
Consolidate expired-ban cleanup into a single block that always returns
next(), use raw keys (req.ip, userId) for banLogs.delete(), add an
expiresAt validity guard, await cache writes with error logging, and
parallelize independent I/O with Promise.all.
Add 25 tests covering all checkBan code paths including the specific
regressions for IP-only cleanup, Redis key mismatch, missing expiresAt,
and cache write failures.
---------
Co-authored-by: Danny Avila
---
api/server/middleware/checkBan.js | 81 ++--
api/test/server/middleware/checkBan.test.js | 426 ++++++++++++++++++++
2 files changed, 469 insertions(+), 38 deletions(-)
create mode 100644 api/test/server/middleware/checkBan.test.js
diff --git a/api/server/middleware/checkBan.js b/api/server/middleware/checkBan.js
index 0c98f3a824..5d1b60297f 100644
--- a/api/server/middleware/checkBan.js
+++ b/api/server/middleware/checkBan.js
@@ -10,6 +10,14 @@ const { findUser } = require('~/models');
const banCache = new Keyv({ store: keyvMongo, namespace: ViolationTypes.BAN, ttl: 0 });
const message = 'Your account has been temporarily banned due to violations of our service.';
+/** @returns {string} Cache key for ban lookups, prefixed for Redis or raw for MongoDB */
+const getBanCacheKey = (prefix, value, useRedis) => {
+ if (!value) {
+ return '';
+ }
+ return useRedis ? `ban_cache:${prefix}:${value}` : value;
+};
+
/**
* Respond to the request if the user is banned.
*
@@ -63,25 +71,16 @@ const checkBan = async (req, res, next = () => {}) => {
return next();
}
- let cachedIPBan;
- let cachedUserBan;
+ const useRedis = isEnabled(process.env.USE_REDIS);
+ const ipKey = getBanCacheKey('ip', req.ip, useRedis);
+ const userKey = getBanCacheKey('user', userId, useRedis);
- let ipKey = '';
- let userKey = '';
+ const [cachedIPBan, cachedUserBan] = await Promise.all([
+ ipKey ? banCache.get(ipKey) : undefined,
+ userKey ? banCache.get(userKey) : undefined,
+ ]);
- if (req.ip) {
- ipKey = isEnabled(process.env.USE_REDIS) ? `ban_cache:ip:${req.ip}` : req.ip;
- cachedIPBan = await banCache.get(ipKey);
- }
-
- if (userId) {
- userKey = isEnabled(process.env.USE_REDIS) ? `ban_cache:user:${userId}` : userId;
- cachedUserBan = await banCache.get(userKey);
- }
-
- const cachedBan = cachedIPBan || cachedUserBan;
-
- if (cachedBan) {
+ if (cachedIPBan || cachedUserBan) {
req.banned = true;
return await banResponse(req, res);
}
@@ -93,41 +92,47 @@ const checkBan = async (req, res, next = () => {}) => {
return next();
}
- let ipBan;
- let userBan;
+ const [ipBan, userBan] = await Promise.all([
+ req.ip ? banLogs.get(req.ip) : undefined,
+ userId ? banLogs.get(userId) : undefined,
+ ]);
- if (req.ip) {
- ipBan = await banLogs.get(req.ip);
- }
+ const banData = ipBan || userBan;
- if (userId) {
- userBan = await banLogs.get(userId);
- }
-
- const isBanned = !!(ipBan || userBan);
-
- if (!isBanned) {
+ if (!banData) {
return next();
}
- const timeLeft = Number(isBanned.expiresAt) - Date.now();
-
- if (timeLeft <= 0 && ipKey) {
- await banLogs.delete(ipKey);
+ const expiresAt = Number(banData.expiresAt);
+ if (!banData.expiresAt || isNaN(expiresAt)) {
+ req.banned = true;
+ return await banResponse(req, res);
}
- if (timeLeft <= 0 && userKey) {
- await banLogs.delete(userKey);
+ const timeLeft = expiresAt - Date.now();
+
+ if (timeLeft <= 0) {
+ const cleanups = [];
+ if (ipBan) {
+ cleanups.push(banLogs.delete(req.ip));
+ }
+ if (userBan) {
+ cleanups.push(banLogs.delete(userId));
+ }
+ await Promise.all(cleanups);
return next();
}
+ const cacheWrites = [];
if (ipKey) {
- banCache.set(ipKey, isBanned, timeLeft);
+ cacheWrites.push(banCache.set(ipKey, banData, timeLeft));
}
-
if (userKey) {
- banCache.set(userKey, isBanned, timeLeft);
+ cacheWrites.push(banCache.set(userKey, banData, timeLeft));
}
+ await Promise.all(cacheWrites).catch((err) =>
+ logger.warn('[checkBan] Failed to write ban cache:', err),
+ );
req.banned = true;
return await banResponse(req, res);
diff --git a/api/test/server/middleware/checkBan.test.js b/api/test/server/middleware/checkBan.test.js
new file mode 100644
index 0000000000..518153be67
--- /dev/null
+++ b/api/test/server/middleware/checkBan.test.js
@@ -0,0 +1,426 @@
+const mockBanCacheGet = jest.fn().mockResolvedValue(undefined);
+const mockBanCacheSet = jest.fn().mockResolvedValue(undefined);
+
+jest.mock('keyv', () => ({
+ Keyv: jest.fn().mockImplementation(() => ({
+ get: mockBanCacheGet,
+ set: mockBanCacheSet,
+ })),
+}));
+
+const mockBanLogsGet = jest.fn().mockResolvedValue(undefined);
+const mockBanLogsDelete = jest.fn().mockResolvedValue(true);
+const mockBanLogs = {
+ get: mockBanLogsGet,
+ delete: mockBanLogsDelete,
+ opts: { ttl: 7200000 },
+};
+
+jest.mock('~/cache', () => ({
+ getLogStores: jest.fn(() => mockBanLogs),
+}));
+
+jest.mock('@librechat/data-schemas', () => ({
+ logger: {
+ info: jest.fn(),
+ warn: jest.fn(),
+ debug: jest.fn(),
+ error: jest.fn(),
+ },
+}));
+
+jest.mock('@librechat/api', () => ({
+ isEnabled: (value) => {
+ if (typeof value === 'boolean') {
+ return value;
+ }
+ if (typeof value === 'string') {
+ return value.toLowerCase().trim() === 'true';
+ }
+ return false;
+ },
+ keyvMongo: {},
+ removePorts: jest.fn((req) => req.ip),
+}));
+
+jest.mock('~/models', () => ({
+ findUser: jest.fn(),
+}));
+
+jest.mock('~/server/middleware/denyRequest', () => jest.fn().mockResolvedValue(undefined));
+
+jest.mock('ua-parser-js', () => jest.fn(() => ({ browser: { name: 'Chrome' } })));
+
+const checkBan = require('~/server/middleware/checkBan');
+const { logger } = require('@librechat/data-schemas');
+const { findUser } = require('~/models');
+
+const createReq = (overrides = {}) => ({
+ ip: '192.168.1.1',
+ user: { id: 'user123' },
+ headers: { 'user-agent': 'Mozilla/5.0' },
+ body: {},
+ baseUrl: '/api',
+ originalUrl: '/api/test',
+ ...overrides,
+});
+
+const createRes = () => ({
+ status: jest.fn().mockReturnThis(),
+ json: jest.fn().mockReturnThis(),
+});
+
+describe('checkBan middleware', () => {
+ let originalEnv;
+
+ beforeEach(() => {
+ originalEnv = { ...process.env };
+ process.env.BAN_VIOLATIONS = 'true';
+ delete process.env.USE_REDIS;
+ mockBanLogs.opts.ttl = 7200000;
+ });
+
+ afterEach(() => {
+ process.env = originalEnv;
+ jest.clearAllMocks();
+ });
+
+ describe('early exits', () => {
+ it('calls next() when BAN_VIOLATIONS is disabled', async () => {
+ process.env.BAN_VIOLATIONS = 'false';
+ const next = jest.fn();
+
+ await checkBan(createReq(), createRes(), next);
+
+ expect(next).toHaveBeenCalledWith();
+ expect(mockBanCacheGet).not.toHaveBeenCalled();
+ });
+
+ it('calls next() when BAN_VIOLATIONS is unset', async () => {
+ delete process.env.BAN_VIOLATIONS;
+ const next = jest.fn();
+
+ await checkBan(createReq(), createRes(), next);
+
+ expect(next).toHaveBeenCalledWith();
+ });
+
+ it('calls next() when neither userId nor IP is available', async () => {
+ const next = jest.fn();
+ const req = createReq({ ip: null, user: null });
+
+ await checkBan(req, createRes(), next);
+
+ expect(next).toHaveBeenCalledWith();
+ });
+
+ it('calls next() when ban duration is <= 0', async () => {
+ mockBanLogs.opts.ttl = 0;
+ const next = jest.fn();
+
+ await checkBan(createReq(), createRes(), next);
+
+ expect(next).toHaveBeenCalledWith();
+ });
+
+ it('calls next() when no ban exists in cache or DB', async () => {
+ const next = jest.fn();
+
+ await checkBan(createReq(), createRes(), next);
+
+ expect(next).toHaveBeenCalledWith();
+ expect(mockBanCacheGet).toHaveBeenCalled();
+ expect(mockBanLogsGet).toHaveBeenCalled();
+ });
+ });
+
+ describe('cache hit path', () => {
+ it('returns 403 when IP ban is cached', async () => {
+ mockBanCacheGet.mockResolvedValueOnce({ expiresAt: Date.now() + 60000 });
+ const next = jest.fn();
+ const req = createReq();
+ const res = createRes();
+
+ await checkBan(req, res, next);
+
+ expect(next).not.toHaveBeenCalled();
+ expect(req.banned).toBe(true);
+ expect(res.status).toHaveBeenCalledWith(403);
+ });
+
+ it('returns 403 when user ban is cached (IP miss)', async () => {
+ mockBanCacheGet
+ .mockResolvedValueOnce(undefined)
+ .mockResolvedValueOnce({ expiresAt: Date.now() + 60000 });
+ const next = jest.fn();
+ const req = createReq();
+ const res = createRes();
+
+ await checkBan(req, res, next);
+
+ expect(next).not.toHaveBeenCalled();
+ expect(req.banned).toBe(true);
+ expect(res.status).toHaveBeenCalledWith(403);
+ });
+
+ it('does not query banLogs when cache hit occurs', async () => {
+ mockBanCacheGet.mockResolvedValueOnce({ expiresAt: Date.now() + 60000 });
+
+ await checkBan(createReq(), createRes(), jest.fn());
+
+ expect(mockBanLogsGet).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('active ban (positive timeLeft)', () => {
+ it('caches ban with correct TTL and returns 403', async () => {
+ const expiresAt = Date.now() + 3600000;
+ const banRecord = { expiresAt, type: 'ban', violation_count: 3 };
+ mockBanLogsGet.mockResolvedValueOnce(banRecord);
+ const next = jest.fn();
+ const req = createReq();
+ const res = createRes();
+
+ await checkBan(req, res, next);
+
+ expect(next).not.toHaveBeenCalled();
+ expect(req.banned).toBe(true);
+ expect(res.status).toHaveBeenCalledWith(403);
+ expect(mockBanCacheSet).toHaveBeenCalledTimes(2);
+
+ const [ipCacheCall, userCacheCall] = mockBanCacheSet.mock.calls;
+ expect(ipCacheCall[0]).toBe('192.168.1.1');
+ expect(ipCacheCall[1]).toBe(banRecord);
+ expect(ipCacheCall[2]).toBeGreaterThan(0);
+ expect(ipCacheCall[2]).toBeLessThanOrEqual(3600000);
+
+ expect(userCacheCall[0]).toBe('user123');
+ expect(userCacheCall[1]).toBe(banRecord);
+ });
+
+ it('caches only IP when no userId is present', async () => {
+ const expiresAt = Date.now() + 3600000;
+ mockBanLogsGet.mockResolvedValueOnce({ expiresAt, type: 'ban' });
+ const req = createReq({ user: null });
+
+ await checkBan(req, createRes(), jest.fn());
+
+ expect(mockBanCacheSet).toHaveBeenCalledTimes(1);
+ expect(mockBanCacheSet).toHaveBeenCalledWith(
+ '192.168.1.1',
+ expect.any(Object),
+ expect.any(Number),
+ );
+ });
+ });
+
+ describe('expired ban cleanup', () => {
+ it('cleans up and calls next() for expired user-key ban', async () => {
+ const expiresAt = Date.now() - 1000;
+ mockBanLogsGet
+ .mockResolvedValueOnce(undefined)
+ .mockResolvedValueOnce({ expiresAt, type: 'ban' });
+ const next = jest.fn();
+ const req = createReq();
+
+ await checkBan(req, createRes(), next);
+
+ expect(next).toHaveBeenCalledWith();
+ expect(req.banned).toBeUndefined();
+ expect(mockBanLogsDelete).toHaveBeenCalledWith('user123');
+ expect(mockBanCacheSet).not.toHaveBeenCalled();
+ });
+
+ it('cleans up and calls next() for expired IP-only ban (Finding 1 regression)', async () => {
+ const expiresAt = Date.now() - 1000;
+ mockBanLogsGet.mockResolvedValueOnce({ expiresAt, type: 'ban' });
+ const next = jest.fn();
+ const req = createReq({ user: null });
+
+ await checkBan(req, createRes(), next);
+
+ expect(next).toHaveBeenCalledWith();
+ expect(req.banned).toBeUndefined();
+ expect(mockBanLogsDelete).toHaveBeenCalledWith('192.168.1.1');
+ expect(mockBanCacheSet).not.toHaveBeenCalled();
+ });
+
+ it('cleans up both IP and user bans when both are expired', async () => {
+ const expiresAt = Date.now() - 1000;
+ mockBanLogsGet
+ .mockResolvedValueOnce({ expiresAt, type: 'ban' })
+ .mockResolvedValueOnce({ expiresAt, type: 'ban' });
+ const next = jest.fn();
+
+ await checkBan(createReq(), createRes(), next);
+
+ expect(next).toHaveBeenCalledWith();
+ expect(mockBanLogsDelete).toHaveBeenCalledTimes(2);
+ expect(mockBanLogsDelete).toHaveBeenCalledWith('192.168.1.1');
+ expect(mockBanLogsDelete).toHaveBeenCalledWith('user123');
+ });
+
+ it('does not write to banCache when ban is expired', async () => {
+ const expiresAt = Date.now() - 60000;
+ mockBanLogsGet.mockResolvedValueOnce({ expiresAt, type: 'ban' });
+
+ await checkBan(createReq({ user: null }), createRes(), jest.fn());
+
+ expect(mockBanCacheSet).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('Redis key paths (Finding 2 regression)', () => {
+ beforeEach(() => {
+ process.env.USE_REDIS = 'true';
+ });
+
+ it('uses cache-prefixed keys for banCache.get', async () => {
+ await checkBan(createReq(), createRes(), jest.fn());
+
+ expect(mockBanCacheGet).toHaveBeenCalledWith('ban_cache:ip:192.168.1.1');
+ expect(mockBanCacheGet).toHaveBeenCalledWith('ban_cache:user:user123');
+ });
+
+ it('uses raw keys (not cache-prefixed) for banLogs.delete on cleanup', async () => {
+ const expiresAt = Date.now() - 1000;
+ mockBanLogsGet
+ .mockResolvedValueOnce({ expiresAt, type: 'ban' })
+ .mockResolvedValueOnce({ expiresAt, type: 'ban' });
+
+ await checkBan(createReq(), createRes(), jest.fn());
+
+ expect(mockBanLogsDelete).toHaveBeenCalledWith('192.168.1.1');
+ expect(mockBanLogsDelete).toHaveBeenCalledWith('user123');
+ for (const call of mockBanLogsDelete.mock.calls) {
+ expect(call[0]).not.toMatch(/^ban_cache:/);
+ }
+ });
+
+ it('uses cache-prefixed keys for banCache.set on active ban', async () => {
+ const expiresAt = Date.now() + 3600000;
+ mockBanLogsGet.mockResolvedValueOnce({ expiresAt, type: 'ban' });
+
+ await checkBan(createReq(), createRes(), jest.fn());
+
+ expect(mockBanCacheSet).toHaveBeenCalledWith(
+ 'ban_cache:ip:192.168.1.1',
+ expect.any(Object),
+ expect.any(Number),
+ );
+ expect(mockBanCacheSet).toHaveBeenCalledWith(
+ 'ban_cache:user:user123',
+ expect.any(Object),
+ expect.any(Number),
+ );
+ });
+ });
+
+ describe('missing expiresAt guard (Finding 5)', () => {
+ it('returns 403 without caching when expiresAt is missing', async () => {
+ mockBanLogsGet.mockResolvedValueOnce({ type: 'ban' });
+ const next = jest.fn();
+ const req = createReq();
+ const res = createRes();
+
+ await checkBan(req, res, next);
+
+ expect(next).not.toHaveBeenCalled();
+ expect(req.banned).toBe(true);
+ expect(res.status).toHaveBeenCalledWith(403);
+ expect(mockBanCacheSet).not.toHaveBeenCalled();
+ });
+
+ it('returns 403 without caching when expiresAt is NaN-producing', async () => {
+ mockBanLogsGet.mockResolvedValueOnce({ type: 'ban', expiresAt: 'not-a-number' });
+ const next = jest.fn();
+ const res = createRes();
+
+ await checkBan(createReq(), res, next);
+
+ expect(next).not.toHaveBeenCalled();
+ expect(res.status).toHaveBeenCalledWith(403);
+ expect(mockBanCacheSet).not.toHaveBeenCalled();
+ });
+
+ it('returns 403 without caching when expiresAt is null', async () => {
+ mockBanLogsGet.mockResolvedValueOnce({ type: 'ban', expiresAt: null });
+ const next = jest.fn();
+ const res = createRes();
+
+ await checkBan(createReq(), res, next);
+
+ expect(next).not.toHaveBeenCalled();
+ expect(res.status).toHaveBeenCalledWith(403);
+ expect(mockBanCacheSet).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('cache write error handling (Finding 4)', () => {
+ it('still returns 403 when banCache.set rejects', async () => {
+ const expiresAt = Date.now() + 3600000;
+ mockBanLogsGet.mockResolvedValueOnce({ expiresAt, type: 'ban' });
+ mockBanCacheSet.mockRejectedValue(new Error('MongoDB write failure'));
+ const next = jest.fn();
+ const req = createReq();
+ const res = createRes();
+
+ await checkBan(req, res, next);
+
+ expect(next).not.toHaveBeenCalled();
+ expect(req.banned).toBe(true);
+ expect(res.status).toHaveBeenCalledWith(403);
+ });
+
+ it('logs a warning when banCache.set fails', async () => {
+ const expiresAt = Date.now() + 3600000;
+ mockBanLogsGet.mockResolvedValueOnce({ expiresAt, type: 'ban' });
+ mockBanCacheSet.mockRejectedValue(new Error('write failed'));
+
+ await checkBan(createReq(), createRes(), jest.fn());
+
+ expect(logger.warn).toHaveBeenCalledWith(
+ '[checkBan] Failed to write ban cache:',
+ expect.any(Error),
+ );
+ });
+ });
+
+ describe('user lookup by email', () => {
+ it('resolves userId from email when not on request', async () => {
+ const req = createReq({ user: null, body: { email: 'test@example.com' } });
+ findUser.mockResolvedValueOnce({ _id: 'resolved-user-id' });
+ const expiresAt = Date.now() + 3600000;
+ mockBanLogsGet
+ .mockResolvedValueOnce(undefined)
+ .mockResolvedValueOnce({ expiresAt, type: 'ban' });
+
+ await checkBan(req, createRes(), jest.fn());
+
+ expect(findUser).toHaveBeenCalledWith({ email: 'test@example.com' }, '_id');
+ expect(req.banned).toBe(true);
+ });
+
+ it('continues with IP-only check when email lookup finds no user', async () => {
+ const req = createReq({ user: null, body: { email: 'unknown@example.com' } });
+ findUser.mockResolvedValueOnce(null);
+ const next = jest.fn();
+
+ await checkBan(req, createRes(), next);
+
+ expect(next).toHaveBeenCalledWith();
+ });
+ });
+
+ describe('error handling', () => {
+ it('calls next(error) when an unexpected error occurs', async () => {
+ mockBanCacheGet.mockRejectedValueOnce(new Error('connection lost'));
+ const next = jest.fn();
+
+ await checkBan(createReq(), createRes(), next);
+
+ expect(next).toHaveBeenCalledWith(expect.any(Error));
+ expect(logger.error).toHaveBeenCalled();
+ });
+ });
+});
From 28c2e224ae0fa0dccf355ff010a89ffb51838a7b Mon Sep 17 00:00:00 2001
From: ethanlaj
Date: Fri, 20 Mar 2026 13:06:04 -0400
Subject: [PATCH 20/98] =?UTF-8?q?=F0=9F=A7=B9=20chore:=20Resolve=20correct?=
=?UTF-8?q?=20memory=20directory=20in=20`.gitignore`=20(#12330)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* fix: Exclude memory directory from gitignore for API package
* fix: Scope memory/ and coordination/ gitignore to repo root
Prefix patterns with `/` so they only match root-level Claude Flow
artifact directories, not workspace source like packages/api/src/memory/.
---------
Co-authored-by: Danny Avila
---
.gitignore | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/.gitignore b/.gitignore
index 86d4a3ddae..980be5b8eb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -154,16 +154,16 @@ claude-flow.config.json
.swarm/
.hive-mind/
.claude-flow/
-memory/
-coordination/
-memory/claude-flow-data.json
-memory/sessions/*
-!memory/sessions/README.md
-memory/agents/*
-!memory/agents/README.md
-coordination/memory_bank/*
-coordination/subtasks/*
-coordination/orchestration/*
+/memory/
+/coordination/
+/memory/claude-flow-data.json
+/memory/sessions/*
+!/memory/sessions/README.md
+/memory/agents/*
+!/memory/agents/README.md
+/coordination/memory_bank/*
+/coordination/subtasks/*
+/coordination/orchestration/*
*.db
*.db-journal
*.db-wal
From 4e5ae28fa90063a36d755fa217d6a0f517a6cc12 Mon Sep 17 00:00:00 2001
From: mfish911 <33205066+mfish911@users.noreply.github.com>
Date: Fri, 20 Mar 2026 13:07:39 -0400
Subject: [PATCH 21/98] =?UTF-8?q?=F0=9F=93=A1=20feat:=20Support=20Unauthen?=
=?UTF-8?q?ticated=20SMTP=20Relays=20(#12322)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* allow smtp server that does not have authentication
* fix: align checkEmailConfig with optional SMTP credentials and add tests
Remove EMAIL_USERNAME/EMAIL_PASSWORD requirements from the hasSMTPConfig
predicate in checkEmailConfig() so the rest of the codebase (login,
startup checks, invite-user) correctly recognizes unauthenticated SMTP
as a valid email configuration.
Add a warning when only one of the two credential env vars is set,
in both sendEmail.js and checkEmailConfig(), to catch partial
misconfigurations early.
Add test coverage for both the transporter auth assembly in sendEmail.js
and the checkEmailConfig predicate in packages/api.
Document in .env.example that credentials are optional for
unauthenticated SMTP relays.
---------
Co-authored-by: Danny Avila
---
.env.example | 1 +
api/server/utils/__tests__/sendEmail.spec.js | 143 ++++++++++++++++++
api/server/utils/sendEmail.js | 15 +-
.../api/src/utils/__tests__/email.test.ts | 120 +++++++++++++++
packages/api/src/utils/email.ts | 17 ++-
5 files changed, 289 insertions(+), 7 deletions(-)
create mode 100644 api/server/utils/__tests__/sendEmail.spec.js
create mode 100644 packages/api/src/utils/__tests__/email.test.ts
diff --git a/.env.example b/.env.example
index 73e95c394c..ae3537038a 100644
--- a/.env.example
+++ b/.env.example
@@ -625,6 +625,7 @@ EMAIL_PORT=25
EMAIL_ENCRYPTION=
EMAIL_ENCRYPTION_HOSTNAME=
EMAIL_ALLOW_SELFSIGNED=
+# Leave both empty for SMTP servers that do not require authentication
EMAIL_USERNAME=
EMAIL_PASSWORD=
EMAIL_FROM_NAME=
diff --git a/api/server/utils/__tests__/sendEmail.spec.js b/api/server/utils/__tests__/sendEmail.spec.js
new file mode 100644
index 0000000000..5c79094c53
--- /dev/null
+++ b/api/server/utils/__tests__/sendEmail.spec.js
@@ -0,0 +1,143 @@
+const nodemailer = require('nodemailer');
+const { readFileAsString } = require('@librechat/api');
+
+jest.mock('nodemailer');
+jest.mock('@librechat/data-schemas', () => ({
+ logger: { debug: jest.fn(), warn: jest.fn(), error: jest.fn() },
+}));
+jest.mock('@librechat/api', () => ({
+ logAxiosError: jest.fn(),
+ isEnabled: jest.fn((val) => val === 'true' || val === true),
+ readFileAsString: jest.fn(),
+}));
+
+const savedEnv = { ...process.env };
+
+const mockSendMail = jest.fn().mockResolvedValue({ messageId: 'test-id' });
+
+beforeEach(() => {
+ jest.clearAllMocks();
+ process.env = { ...savedEnv };
+ process.env.EMAIL_HOST = 'smtp.example.com';
+ process.env.EMAIL_PORT = '587';
+ process.env.EMAIL_FROM = 'noreply@example.com';
+ process.env.APP_TITLE = 'TestApp';
+ delete process.env.EMAIL_USERNAME;
+ delete process.env.EMAIL_PASSWORD;
+ delete process.env.MAILGUN_API_KEY;
+ delete process.env.MAILGUN_DOMAIN;
+ delete process.env.EMAIL_SERVICE;
+ delete process.env.EMAIL_ENCRYPTION;
+ delete process.env.EMAIL_ENCRYPTION_HOSTNAME;
+ delete process.env.EMAIL_ALLOW_SELFSIGNED;
+
+ readFileAsString.mockResolvedValue({ content: '{{name}}
' });
+ nodemailer.createTransport.mockReturnValue({ sendMail: mockSendMail });
+});
+
+afterAll(() => {
+ process.env = savedEnv;
+});
+
+/** Loads a fresh copy of sendEmail so process.env reads are re-evaluated. */
+function loadSendEmail() {
+ jest.resetModules();
+ jest.mock('nodemailer', () => ({
+ createTransport: jest.fn().mockReturnValue({ sendMail: mockSendMail }),
+ }));
+ jest.mock('@librechat/data-schemas', () => ({
+ logger: { debug: jest.fn(), warn: jest.fn(), error: jest.fn() },
+ }));
+ jest.mock('@librechat/api', () => ({
+ logAxiosError: jest.fn(),
+ isEnabled: jest.fn((val) => val === 'true' || val === true),
+ readFileAsString: jest.fn().mockResolvedValue({ content: '{{name}}
' }),
+ }));
+ return require('../sendEmail');
+}
+
+const baseParams = {
+ email: 'user@example.com',
+ subject: 'Test',
+ payload: { name: 'User' },
+ template: 'test.handlebars',
+};
+
+describe('sendEmail SMTP auth assembly', () => {
+ it('includes auth when both EMAIL_USERNAME and EMAIL_PASSWORD are set', async () => {
+ process.env.EMAIL_USERNAME = 'smtp_user';
+ process.env.EMAIL_PASSWORD = 'smtp_pass';
+ const sendEmail = loadSendEmail();
+ const { createTransport } = require('nodemailer');
+
+ await sendEmail(baseParams);
+
+ expect(createTransport).toHaveBeenCalledTimes(1);
+ const transporterOptions = createTransport.mock.calls[0][0];
+ expect(transporterOptions.auth).toEqual({
+ user: 'smtp_user',
+ pass: 'smtp_pass',
+ });
+ });
+
+ it('omits auth when both EMAIL_USERNAME and EMAIL_PASSWORD are absent', async () => {
+ const sendEmail = loadSendEmail();
+ const { createTransport } = require('nodemailer');
+
+ await sendEmail(baseParams);
+
+ expect(createTransport).toHaveBeenCalledTimes(1);
+ const transporterOptions = createTransport.mock.calls[0][0];
+ expect(transporterOptions.auth).toBeUndefined();
+ });
+
+ it('omits auth and logs a warning when only EMAIL_USERNAME is set', async () => {
+ process.env.EMAIL_USERNAME = 'smtp_user';
+ const sendEmail = loadSendEmail();
+ const { createTransport } = require('nodemailer');
+ const { logger: freshLogger } = require('@librechat/data-schemas');
+
+ await sendEmail(baseParams);
+
+ const transporterOptions = createTransport.mock.calls[0][0];
+ expect(transporterOptions.auth).toBeUndefined();
+ expect(freshLogger.warn).toHaveBeenCalledWith(
+ expect.stringContaining('EMAIL_USERNAME and EMAIL_PASSWORD must both be set'),
+ );
+ });
+
+ it('omits auth and logs a warning when only EMAIL_PASSWORD is set', async () => {
+ process.env.EMAIL_PASSWORD = 'smtp_pass';
+ const sendEmail = loadSendEmail();
+ const { createTransport } = require('nodemailer');
+ const { logger: freshLogger } = require('@librechat/data-schemas');
+
+ await sendEmail(baseParams);
+
+ const transporterOptions = createTransport.mock.calls[0][0];
+ expect(transporterOptions.auth).toBeUndefined();
+ expect(freshLogger.warn).toHaveBeenCalledWith(
+ expect.stringContaining('EMAIL_USERNAME and EMAIL_PASSWORD must both be set'),
+ );
+ });
+
+ it('does not log a warning when both credentials are properly set', async () => {
+ process.env.EMAIL_USERNAME = 'smtp_user';
+ process.env.EMAIL_PASSWORD = 'smtp_pass';
+ const sendEmail = loadSendEmail();
+ const { logger: freshLogger } = require('@librechat/data-schemas');
+
+ await sendEmail(baseParams);
+
+ expect(freshLogger.warn).not.toHaveBeenCalled();
+ });
+
+ it('does not log a warning when both credentials are absent', async () => {
+ const sendEmail = loadSendEmail();
+ const { logger: freshLogger } = require('@librechat/data-schemas');
+
+ await sendEmail(baseParams);
+
+ expect(freshLogger.warn).not.toHaveBeenCalled();
+ });
+});
diff --git a/api/server/utils/sendEmail.js b/api/server/utils/sendEmail.js
index 432a571ffb..3fa3e6fcba 100644
--- a/api/server/utils/sendEmail.js
+++ b/api/server/utils/sendEmail.js
@@ -124,11 +124,20 @@ const sendEmail = async ({ email, subject, payload, template, throwError = true
// Whether to accept unsigned certificates
rejectUnauthorized: !isEnabled(process.env.EMAIL_ALLOW_SELFSIGNED),
},
- auth: {
+ };
+
+ const hasUsername = !!process.env.EMAIL_USERNAME;
+ const hasPassword = !!process.env.EMAIL_PASSWORD;
+ if (hasUsername && hasPassword) {
+ transporterOptions.auth = {
user: process.env.EMAIL_USERNAME,
pass: process.env.EMAIL_PASSWORD,
- },
- };
+ };
+ } else if (hasUsername !== hasPassword) {
+ logger.warn(
+ '[sendEmail] EMAIL_USERNAME and EMAIL_PASSWORD must both be set for authenticated SMTP, or both omitted for unauthenticated SMTP. Proceeding without authentication.',
+ );
+ }
if (process.env.EMAIL_ENCRYPTION_HOSTNAME) {
// Check the certificate against this name explicitly
diff --git a/packages/api/src/utils/__tests__/email.test.ts b/packages/api/src/utils/__tests__/email.test.ts
new file mode 100644
index 0000000000..ccbd0aabfe
--- /dev/null
+++ b/packages/api/src/utils/__tests__/email.test.ts
@@ -0,0 +1,120 @@
+import { logger } from '@librechat/data-schemas';
+import { checkEmailConfig } from '../email';
+
+jest.mock('@librechat/data-schemas', () => ({
+ logger: { warn: jest.fn() },
+}));
+
+const savedEnv = { ...process.env };
+
+beforeEach(() => {
+ jest.clearAllMocks();
+ process.env = { ...savedEnv };
+ delete process.env.EMAIL_SERVICE;
+ delete process.env.EMAIL_HOST;
+ delete process.env.EMAIL_USERNAME;
+ delete process.env.EMAIL_PASSWORD;
+ delete process.env.EMAIL_FROM;
+ delete process.env.MAILGUN_API_KEY;
+ delete process.env.MAILGUN_DOMAIN;
+});
+
+afterAll(() => {
+ process.env = savedEnv;
+});
+
+describe('checkEmailConfig', () => {
+ describe('SMTP configuration', () => {
+ it('returns true with EMAIL_HOST and EMAIL_FROM (no credentials)', () => {
+ process.env.EMAIL_HOST = 'smtp.example.com';
+ process.env.EMAIL_FROM = 'noreply@example.com';
+ expect(checkEmailConfig()).toBe(true);
+ });
+
+ it('returns true with EMAIL_SERVICE and EMAIL_FROM (no credentials)', () => {
+ process.env.EMAIL_SERVICE = 'gmail';
+ process.env.EMAIL_FROM = 'noreply@example.com';
+ expect(checkEmailConfig()).toBe(true);
+ });
+
+ it('returns true with EMAIL_HOST, EMAIL_FROM, and full credentials', () => {
+ process.env.EMAIL_HOST = 'smtp.example.com';
+ process.env.EMAIL_FROM = 'noreply@example.com';
+ process.env.EMAIL_USERNAME = 'user';
+ process.env.EMAIL_PASSWORD = 'pass';
+ expect(checkEmailConfig()).toBe(true);
+ });
+
+ it('returns false when EMAIL_FROM is missing', () => {
+ process.env.EMAIL_HOST = 'smtp.example.com';
+ expect(checkEmailConfig()).toBe(false);
+ });
+
+ it('returns false when neither EMAIL_HOST nor EMAIL_SERVICE is set', () => {
+ process.env.EMAIL_FROM = 'noreply@example.com';
+ expect(checkEmailConfig()).toBe(false);
+ });
+
+ it('returns false when no email env vars are set', () => {
+ expect(checkEmailConfig()).toBe(false);
+ });
+ });
+
+ describe('partial credential warning', () => {
+ it('logs a warning when only EMAIL_USERNAME is set', () => {
+ process.env.EMAIL_HOST = 'smtp.example.com';
+ process.env.EMAIL_FROM = 'noreply@example.com';
+ process.env.EMAIL_USERNAME = 'user';
+ checkEmailConfig();
+ expect(logger.warn).toHaveBeenCalledWith(
+ expect.stringContaining('EMAIL_USERNAME and EMAIL_PASSWORD must both be set'),
+ );
+ });
+
+ it('logs a warning when only EMAIL_PASSWORD is set', () => {
+ process.env.EMAIL_HOST = 'smtp.example.com';
+ process.env.EMAIL_FROM = 'noreply@example.com';
+ process.env.EMAIL_PASSWORD = 'pass';
+ checkEmailConfig();
+ expect(logger.warn).toHaveBeenCalledWith(
+ expect.stringContaining('EMAIL_USERNAME and EMAIL_PASSWORD must both be set'),
+ );
+ });
+
+ it('does not warn when both credentials are set', () => {
+ process.env.EMAIL_HOST = 'smtp.example.com';
+ process.env.EMAIL_FROM = 'noreply@example.com';
+ process.env.EMAIL_USERNAME = 'user';
+ process.env.EMAIL_PASSWORD = 'pass';
+ checkEmailConfig();
+ expect(logger.warn).not.toHaveBeenCalled();
+ });
+
+ it('does not warn when neither credential is set', () => {
+ process.env.EMAIL_HOST = 'smtp.example.com';
+ process.env.EMAIL_FROM = 'noreply@example.com';
+ checkEmailConfig();
+ expect(logger.warn).not.toHaveBeenCalled();
+ });
+
+ it('does not warn for partial credentials when SMTP is not configured', () => {
+ process.env.EMAIL_USERNAME = 'user';
+ checkEmailConfig();
+ expect(logger.warn).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('Mailgun configuration', () => {
+ it('returns true with Mailgun API key, domain, and EMAIL_FROM', () => {
+ process.env.MAILGUN_API_KEY = 'key-abc123';
+ process.env.MAILGUN_DOMAIN = 'mg.example.com';
+ process.env.EMAIL_FROM = 'noreply@example.com';
+ expect(checkEmailConfig()).toBe(true);
+ });
+
+ it('returns false when Mailgun is partially configured', () => {
+ process.env.MAILGUN_API_KEY = 'key-abc123';
+ expect(checkEmailConfig()).toBe(false);
+ });
+ });
+});
diff --git a/packages/api/src/utils/email.ts b/packages/api/src/utils/email.ts
index f98e7c51be..6f9171a43b 100644
--- a/packages/api/src/utils/email.ts
+++ b/packages/api/src/utils/email.ts
@@ -1,3 +1,5 @@
+import { logger } from '@librechat/data-schemas';
+
/**
* Check if email configuration is set
* @returns Returns `true` if either Mailgun or SMTP is properly configured
@@ -7,10 +9,17 @@ export function checkEmailConfig(): boolean {
!!process.env.MAILGUN_API_KEY && !!process.env.MAILGUN_DOMAIN && !!process.env.EMAIL_FROM;
const hasSMTPConfig =
- (!!process.env.EMAIL_SERVICE || !!process.env.EMAIL_HOST) &&
- !!process.env.EMAIL_USERNAME &&
- !!process.env.EMAIL_PASSWORD &&
- !!process.env.EMAIL_FROM;
+ (!!process.env.EMAIL_SERVICE || !!process.env.EMAIL_HOST) && !!process.env.EMAIL_FROM;
+
+ if (hasSMTPConfig) {
+ const hasUsername = !!process.env.EMAIL_USERNAME;
+ const hasPassword = !!process.env.EMAIL_PASSWORD;
+ if (hasUsername !== hasPassword) {
+ logger.warn(
+ '[checkEmailConfig] EMAIL_USERNAME and EMAIL_PASSWORD must both be set for authenticated SMTP, or both omitted for unauthenticated SMTP.',
+ );
+ }
+ }
return hasMailgunConfig || hasSMTPConfig;
}
From 59873e74fc3cd6fe43230e59342599c9c1f8fadb Mon Sep 17 00:00:00 2001
From: YE <69640321+JasonYeYuhe@users.noreply.github.com>
Date: Sat, 21 Mar 2026 02:08:48 +0900
Subject: [PATCH 22/98] =?UTF-8?q?=F0=9F=8F=AE=20docs:=20Add=20Simplified?=
=?UTF-8?q?=20Chinese=20README=20Translation=20(#12323)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* docs: add Simplified Chinese translation (README.zh.md)
* docs: sync README.zh.md with current English README
Address review findings:
- Fix stale Railway URLs (railway.app -> railway.com, /template/ -> /deploy/)
- Add missing Resumable Streams section
- Add missing sub-features (Agent Marketplace, Collaborative Sharing, Jina
Reranking, prompt sharing, Helicone provider)
- Update multilingual UI language list from 18 to 30+ languages
- Replace outdated "ChatGPT clone" platform description with current messaging
- Remove stale video embed no longer in English README
- Remove non-standard bold wrappers on links
- Fix trailing whitespace
- Add sync header with date and commit SHA
---------
Co-authored-by: Danny Avila
---
README.md | 5 ++
README.zh.md | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 232 insertions(+)
create mode 100644 README.zh.md
diff --git a/README.md b/README.md
index e82b3ebc2c..7da34974e3 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,11 @@
+
+ English ·
+ 中文
+
+
+
+
+
+
+
+
+
+
+
+ English ·
+ 中文
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# ✨ 功能
+
+- 🖥️ **UI 与体验**:受 ChatGPT 启发,并具备更强的设计与功能。
+
+- 🤖 **AI 模型选择**:
+ - Anthropic (Claude), AWS Bedrock, OpenAI, Azure OpenAI, Google, Vertex AI, OpenAI Responses API (包含 Azure)
+ - [自定义端点 (Custom Endpoints)](https://www.librechat.ai/docs/quick_start/custom_endpoints):LibreChat 支持任何兼容 OpenAI 规范的 API,无需代理。
+ - 兼容[本地与远程 AI 服务商](https://www.librechat.ai/docs/configuration/librechat_yaml/ai_endpoints):
+ - Ollama, groq, Cohere, Mistral AI, Apple MLX, koboldcpp, together.ai,
+ - OpenRouter, Helicone, Perplexity, ShuttleAI, Deepseek, Qwen 等。
+
+- 🔧 **[代码解释器 (Code Interpreter) API](https://www.librechat.ai/docs/features/code_interpreter)**:
+ - 安全的沙箱执行环境,支持 Python, Node.js (JS/TS), Go, C/C++, Java, PHP, Rust 和 Fortran。
+ - 无缝文件处理:直接上传、处理并下载文件。
+ - 隐私无忧:完全隔离且安全的执行环境。
+
+- 🔦 **智能体与工具集成**:
+ - **[LibreChat 智能体 (Agents)](https://www.librechat.ai/docs/features/agents)**:
+ - 无代码定制助手:无需编程即可构建专业化的 AI 驱动助手。
+ - 智能体市场:发现并部署社区构建的智能体。
+ - 协作共享:与特定用户和群组共享智能体。
+ - 灵活且可扩展:支持 MCP 服务器、工具、文件搜索、代码执行等。
+ - 兼容自定义端点、OpenAI, Azure, Anthropic, AWS Bedrock, Google, Vertex AI, Responses API 等。
+ - [支持模型上下文协议 (MCP)](https://modelcontextprotocol.io/clients#librechat) 用于工具调用。
+
+- 🔍 **网页搜索**:
+ - 搜索互联网并检索相关信息以增强 AI 上下文。
+ - 结合搜索提供商、内容爬虫和结果重排序,确保最佳检索效果。
+ - **可定制 Jina 重排序**:配置自定义 Jina API URL 用于重排序服务。
+ - **[了解更多 →](https://www.librechat.ai/docs/features/web_search)**
+
+- 🪄 **支持代码 Artifacts 的生成式 UI**:
+ - [代码 Artifacts](https://youtu.be/GfTj7O4gmd0?si=WJbdnemZpJzBrJo3) 允许在对话中直接创建 React 组件、HTML 页面和 Mermaid 图表。
+
+- 🎨 **图像生成与编辑**:
+ - 使用 [GPT-Image-1](https://www.librechat.ai/docs/features/image_gen#1--openai-image-tools-recommended) 进行文生图与图生图。
+ - 支持 [DALL-E (3/2)](https://www.librechat.ai/docs/features/image_gen#2--dalle-legacy), [Stable Diffusion](https://www.librechat.ai/docs/features/image_gen#3--stable-diffusion-local), [Flux](https://www.librechat.ai/docs/features/image_gen#4--flux) 或任何 [MCP 服务器](https://www.librechat.ai/docs/features/image_gen#5--model-context-protocol-mcp)。
+ - 根据提示词生成惊艳的视觉效果,或通过指令精修现有图像。
+
+- 💾 **预设与上下文管理**:
+ - 创建、保存并分享自定义预设。
+ - 在对话中随时切换 AI 端点和预设。
+ - 编辑、重新提交并通过对话分支继续消息。
+ - 创建并与特定用户和群组共享提示词。
+ - [消息与对话分叉 (Fork)](https://www.librechat.ai/docs/features/fork) 以实现高级上下文控制。
+
+- 💬 **多模态与文件交互**:
+ - 使用 Claude 3, GPT-4.5, GPT-4o, o1, Llama-Vision 和 Gemini 上传并分析图像 📸。
+ - 支持通过自定义端点、OpenAI, Azure, Anthropic, AWS Bedrock 和 Google 进行文件对话 🗃️。
+
+- 🌎 **多语言 UI**:
+ - English, 中文 (简体), 中文 (繁體), العربية, Deutsch, Español, Français, Italiano
+ - Polski, Português (PT), Português (BR), Русский, 日本語, Svenska, 한국어, Tiếng Việt
+ - Türkçe, Nederlands, עברית, Català, Čeština, Dansk, Eesti, فارسی
+ - Suomi, Magyar, Հայերեն, Bahasa Indonesia, ქართული, Latviešu, ไทย, ئۇيغۇرچە
+
+- 🧠 **推理 UI**:
+ - 针对 DeepSeek-R1 等思维链/推理 AI 模型的动态推理 UI。
+
+- 🎨 **可定制界面**:
+ - 可定制的下拉菜单和界面,同时适配高级用户和初学者。
+
+- 🌊 **[可恢复流 (Resumable Streams)](https://www.librechat.ai/docs/features/resumable_streams)**:
+ - 永不丢失响应:AI 响应在连接中断后自动重连并继续。
+ - 多标签页与多设备同步:在多个标签页打开同一对话,或在另一设备上继续。
+ - 生产级可靠性:支持从单机部署到基于 Redis 的水平扩展。
+
+- 🗣️ **语音与音频**:
+ - 通过语音转文字和文字转语音实现免提对话。
+ - 自动发送并播放音频。
+ - 支持 OpenAI, Azure OpenAI 和 Elevenlabs。
+
+- 📥 **导入与导出对话**:
+ - 从 LibreChat, ChatGPT, Chatbot UI 导入对话。
+ - 将对话导出为截图、Markdown、文本、JSON。
+
+- 🔍 **搜索与发现**:
+ - 搜索所有消息和对话。
+
+- 👥 **多用户与安全访问**:
+ - 支持 OAuth2, LDAP 和电子邮件登录的多用户安全认证。
+ - 内置审核系统和 Token 消耗管理工具。
+
+- ⚙️ **配置与部署**:
+ - 支持代理、反向代理、Docker 及多种部署选项。
+ - 可完全本地运行或部署在云端。
+
+- 📖 **开源与社区**:
+ - 完全开源且在公众监督下开发。
+ - 社区驱动的开发、支持与反馈。
+
+[查看我们的文档了解更多功能详情](https://docs.librechat.ai/) 📚
+
+## 🪶 LibreChat:全方位的 AI 对话平台
+
+LibreChat 是一个自托管的 AI 对话平台,在一个注重隐私的统一界面中整合了所有主流 AI 服务商。
+
+除了对话功能外,LibreChat 还提供 AI 智能体、模型上下文协议 (MCP) 支持、Artifacts、代码解释器、自定义操作、对话搜索,以及企业级多用户认证。
+
+开源、活跃开发中,专为重视 AI 基础设施自主可控的用户而构建。
+
+---
+
+## 🌐 资源
+
+**GitHub 仓库:**
+ - **RAG API:** [github.com/danny-avila/rag_api](https://github.com/danny-avila/rag_api)
+ - **网站:** [github.com/LibreChat-AI/librechat.ai](https://github.com/LibreChat-AI/librechat.ai)
+
+**其他:**
+ - **官方网站:** [librechat.ai](https://librechat.ai)
+ - **帮助文档:** [librechat.ai/docs](https://librechat.ai/docs)
+ - **博客:** [librechat.ai/blog](https://librechat.ai/blog)
+
+---
+
+## 📝 更新日志
+
+访问发布页面和更新日志以了解最新动态:
+- [发布页面 (Releases)](https://github.com/danny-avila/LibreChat/releases)
+- [更新日志 (Changelog)](https://www.librechat.ai/changelog)
+
+**⚠️ 在更新前请务必查看[更新日志](https://www.librechat.ai/changelog)以了解破坏性更改。**
+
+---
+
+## ⭐ Star 历史
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+---
+
+## ✨ 贡献
+
+欢迎任何形式的贡献、建议、错误报告和修复!
+
+对于新功能、组件或扩展,请在发送 PR 前开启 issue 进行讨论。
+
+如果您想帮助我们将 LibreChat 翻译成您的母语,我们非常欢迎!改进翻译不仅能让全球用户更轻松地使用 LibreChat,还能提升整体用户体验。请查看我们的[翻译指南](https://www.librechat.ai/docs/translation)。
+
+---
+
+## 💖 感谢所有贡献者
+
+
+
+
+
+---
+
+## 🎉 特别鸣谢
+
+感谢 [Locize](https://locize.com) 提供的翻译管理工具,支持 LibreChat 的多语言功能。
+
+
+
+
+
+
From b66f7914a5f62ea478ba9ccd3ce98b83bfc1bf52 Mon Sep 17 00:00:00 2001
From: Danny Avila
Date: Fri, 20 Mar 2026 13:31:08 -0400
Subject: [PATCH 23/98] =?UTF-8?q?=E2=9B=93=EF=B8=8F=E2=80=8D=F0=9F=92=A5?=
=?UTF-8?q?=20fix:=20Replace=20React=20Markdown=20Artifact=20Renderer=20wi?=
=?UTF-8?q?th=20Static=20HTML=20(#12337)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The react-markdown dependency chain uses Node.js subpath imports
(vfile/lib/#minpath) that Sandpack's bundler cannot resolve, breaking
markdown artifact preview. Switch to a self-contained static HTML page
using marked.js from CDN, eliminating the React bootstrap overhead and
the problematic dependency resolution.
---
.../__tests__/useArtifactProps.test.ts | 33 +--
client/src/utils/__tests__/markdown.test.ts | 201 +++++++++++-------
client/src/utils/artifacts.ts | 18 +-
client/src/utils/markdown.ts | 170 +++++++--------
4 files changed, 224 insertions(+), 198 deletions(-)
diff --git a/client/src/hooks/Artifacts/__tests__/useArtifactProps.test.ts b/client/src/hooks/Artifacts/__tests__/useArtifactProps.test.ts
index e46a285c50..5ffd52879f 100644
--- a/client/src/hooks/Artifacts/__tests__/useArtifactProps.test.ts
+++ b/client/src/hooks/Artifacts/__tests__/useArtifactProps.test.ts
@@ -19,7 +19,7 @@ describe('useArtifactProps', () => {
const { result } = renderHook(() => useArtifactProps({ artifact }));
expect(result.current.fileKey).toBe('content.md');
- expect(result.current.template).toBe('react-ts');
+ expect(result.current.template).toBe('static');
});
it('should handle text/plain type with content.md as fileKey', () => {
@@ -31,7 +31,7 @@ describe('useArtifactProps', () => {
const { result } = renderHook(() => useArtifactProps({ artifact }));
expect(result.current.fileKey).toBe('content.md');
- expect(result.current.template).toBe('react-ts');
+ expect(result.current.template).toBe('static');
});
it('should include content.md in files with original markdown', () => {
@@ -46,7 +46,7 @@ describe('useArtifactProps', () => {
expect(result.current.files['content.md']).toBe(markdownContent);
});
- it('should include App.tsx with wrapped markdown renderer', () => {
+ it('should include index.html with static markdown rendering', () => {
const artifact = createArtifact({
type: 'text/markdown',
content: '# Test',
@@ -54,8 +54,8 @@ describe('useArtifactProps', () => {
const { result } = renderHook(() => useArtifactProps({ artifact }));
- expect(result.current.files['App.tsx']).toContain('MarkdownRenderer');
- expect(result.current.files['App.tsx']).toContain('import React from');
+ expect(result.current.files['index.html']).toContain('');
+ expect(result.current.files['index.html']).toContain('marked.parse');
});
it('should include all required markdown files', () => {
@@ -66,12 +66,8 @@ describe('useArtifactProps', () => {
const { result } = renderHook(() => useArtifactProps({ artifact }));
- // Check all required files are present
expect(result.current.files['content.md']).toBeDefined();
- expect(result.current.files['App.tsx']).toBeDefined();
- expect(result.current.files['index.tsx']).toBeDefined();
- expect(result.current.files['/components/ui/MarkdownRenderer.tsx']).toBeDefined();
- expect(result.current.files['markdown.css']).toBeDefined();
+ expect(result.current.files['index.html']).toBeDefined();
});
it('should escape special characters in markdown content', () => {
@@ -82,13 +78,11 @@ describe('useArtifactProps', () => {
const { result } = renderHook(() => useArtifactProps({ artifact }));
- // Original content should be preserved in content.md
expect(result.current.files['content.md']).toContain('`const x = 1;`');
expect(result.current.files['content.md']).toContain('C:\\Users');
- // App.tsx should have escaped content
- expect(result.current.files['App.tsx']).toContain('\\`');
- expect(result.current.files['App.tsx']).toContain('\\\\');
+ expect(result.current.files['index.html']).toContain('\\`');
+ expect(result.current.files['index.html']).toContain('\\\\');
});
it('should handle empty markdown content', () => {
@@ -112,7 +106,7 @@ describe('useArtifactProps', () => {
expect(result.current.files['content.md']).toBe('# No content provided');
});
- it('should provide react-markdown dependency', () => {
+ it('should have no custom dependencies for markdown (uses CDN)', () => {
const artifact = createArtifact({
type: 'text/markdown',
content: '# Test',
@@ -120,9 +114,8 @@ describe('useArtifactProps', () => {
const { result } = renderHook(() => useArtifactProps({ artifact }));
- expect(result.current.sharedProps.customSetup?.dependencies).toHaveProperty('react-markdown');
- expect(result.current.sharedProps.customSetup?.dependencies).toHaveProperty('remark-gfm');
- expect(result.current.sharedProps.customSetup?.dependencies).toHaveProperty('remark-breaks');
+ const deps = result.current.sharedProps.customSetup?.dependencies ?? {};
+ expect(deps).toEqual({});
});
it('should update files when content changes', () => {
@@ -137,7 +130,6 @@ describe('useArtifactProps', () => {
expect(result.current.files['content.md']).toBe('# Original');
- // Update the artifact content
const updatedArtifact = createArtifact({
...artifact,
content: '# Updated',
@@ -201,8 +193,6 @@ describe('useArtifactProps', () => {
const { result } = renderHook(() => useArtifactProps({ artifact }));
- // Language parameter should not affect markdown handling
- // It checks the type directly, not the key
expect(result.current.fileKey).toBe('content.md');
expect(result.current.files['content.md']).toBe('# Test');
});
@@ -214,7 +204,6 @@ describe('useArtifactProps', () => {
const { result } = renderHook(() => useArtifactProps({ artifact }));
- // Should use default behavior
expect(result.current.template).toBe('static');
});
});
diff --git a/client/src/utils/__tests__/markdown.test.ts b/client/src/utils/__tests__/markdown.test.ts
index 9734e0e18a..9834f034e9 100644
--- a/client/src/utils/__tests__/markdown.test.ts
+++ b/client/src/utils/__tests__/markdown.test.ts
@@ -1,4 +1,4 @@
-import { isSafeUrl, getMarkdownFiles } from '../markdown';
+import { isSafeUrl, getMarkdownFiles, EMBEDDED_IS_SAFE_URL } from '../markdown';
describe('isSafeUrl', () => {
it('allows https URLs', () => {
@@ -68,6 +68,37 @@ describe('isSafeUrl', () => {
});
});
+describe('isSafeUrl sync verification', () => {
+ const embeddedFn = new Function('url', EMBEDDED_IS_SAFE_URL + '\nreturn isSafeUrl(url);') as (
+ url: string,
+ ) => boolean;
+
+ const cases: [string, boolean][] = [
+ ['https://example.com', true],
+ ['http://example.com', true],
+ ['mailto:a@b.com', true],
+ ['tel:+1234567890', true],
+ ['/relative', true],
+ ['./relative', true],
+ ['../up', true],
+ ['#anchor', true],
+ ['javascript:alert(1)', false],
+ [' javascript:void(0)', false],
+ ['data:text/html,x', false],
+ ['blob:http://x.com/uuid', false],
+ ['vbscript:run', false],
+ ['file:///etc/passwd', false],
+ ['custom:payload', false],
+ ['', false],
+ [' ', false],
+ ];
+
+ it.each(cases)('embedded copy matches exported isSafeUrl for %j → %s', (url, expected) => {
+ expect(embeddedFn(url)).toBe(expected);
+ expect(isSafeUrl(url)).toBe(expected);
+ });
+});
+
describe('markdown artifacts', () => {
describe('getMarkdownFiles', () => {
it('should return content.md with the original markdown content', () => {
@@ -83,46 +114,26 @@ describe('markdown artifacts', () => {
expect(files['content.md']).toBe('# No content provided');
});
- it('should include App.tsx with MarkdownRenderer component', () => {
+ it('should include index.html with static markdown rendering', () => {
const markdown = '# Test';
const files = getMarkdownFiles(markdown);
- expect(files['App.tsx']).toContain('import React from');
- expect(files['App.tsx']).toContain(
- "import MarkdownRenderer from '/components/ui/MarkdownRenderer'",
- );
- expect(files['App.tsx']).toContain('');
+ expect(files['index.html']).toContain('marked.min.js');
+ expect(files['index.html']).toContain('marked.parse');
+ expect(files['index.html']).toContain('# Test');
});
- it('should include index.tsx entry point', () => {
- const markdown = '# Test';
- const files = getMarkdownFiles(markdown);
-
- expect(files['index.tsx']).toContain('import App from "./App"');
- expect(files['index.tsx']).toContain('import "./styles.css"');
- expect(files['index.tsx']).toContain('import "./markdown.css"');
- expect(files['index.tsx']).toContain('createRoot');
+ it('should only produce content.md and index.html', () => {
+ const files = getMarkdownFiles('# Test');
+ expect(Object.keys(files).sort()).toEqual(['content.md', 'index.html']);
});
- it('should include MarkdownRenderer component file', () => {
- const markdown = '# Test';
- const files = getMarkdownFiles(markdown);
-
- expect(files['/components/ui/MarkdownRenderer.tsx']).toContain('import ReactMarkdown from');
- expect(files['/components/ui/MarkdownRenderer.tsx']).toContain('MarkdownRendererProps');
- expect(files['/components/ui/MarkdownRenderer.tsx']).toContain(
- 'export default MarkdownRenderer',
- );
- });
-
- it('should include markdown.css with styling', () => {
- const markdown = '# Test';
- const files = getMarkdownFiles(markdown);
-
- expect(files['markdown.css']).toContain('.markdown-body');
- expect(files['markdown.css']).toContain('list-style-type: disc');
- expect(files['markdown.css']).toContain('prefers-color-scheme: dark');
+ it('should include markdown CSS in index.html', () => {
+ const files = getMarkdownFiles('# Test');
+ expect(files['index.html']).toContain('.markdown-body');
+ expect(files['index.html']).toContain('list-style-type: disc');
+ expect(files['index.html']).toContain('prefers-color-scheme: dark');
});
describe('content escaping', () => {
@@ -130,29 +141,36 @@ describe('markdown artifacts', () => {
const markdown = 'Here is some `inline code`';
const files = getMarkdownFiles(markdown);
- expect(files['App.tsx']).toContain('\\`');
+ expect(files['index.html']).toContain('\\`');
});
it('should escape backslashes in markdown content', () => {
const markdown = 'Path: C:\\Users\\Test';
const files = getMarkdownFiles(markdown);
- expect(files['App.tsx']).toContain('\\\\');
+ expect(files['index.html']).toContain('\\\\');
});
it('should escape dollar signs in markdown content', () => {
const markdown = 'Price: $100';
const files = getMarkdownFiles(markdown);
- expect(files['App.tsx']).toContain('\\$');
+ expect(files['index.html']).toContain('\\$');
});
it('should handle code blocks with backticks', () => {
const markdown = '```js\nconsole.log("test");\n```';
const files = getMarkdownFiles(markdown);
- // Should be escaped
- expect(files['App.tsx']).toContain('\\`\\`\\`');
+ expect(files['index.html']).toContain('\\`\\`\\`');
+ });
+
+ it('should prevent in content from breaking out of the script block', () => {
+ const markdown = 'Some content with ';
+ const files = getMarkdownFiles(markdown);
+
+ expect(files['index.html']).not.toContain('
+
+